View Issue Details

IDProjectCategoryView StatusLast Update
0035261FPCCompilerpublic2020-03-30 11:24
ReporterRyan Joseph Assigned To 
PrioritynormalSeverityminorReproducibilityN/A
Status newResolutionopen 
Product Version3.3.1 
Summary0035261: [PATCH] implicit specialization for generic routines
DescriptionPutting this patch into the pipeline finally since I believe it to be finished, although I haven't got much feedback on the implementation. Tests with the prefix tgenimspez*.pp are included.

Full source is at https://github.com/genericptr/freepascal/tree/generic_implicit.

Additional InformationPlease note that the no-op lines in htypechk.pas are strange line endings (0x0d) which I believe to be errors in the original sources since I have not encountered this is any other file.
Tagsgenerics
Fixed in Revision
FPCOldBugId
FPCTarget
Attached Files

Activities

Ryan Joseph

2019-03-23 14:43

reporter  

gen-implicit.diff (65,338 bytes)   
From c57b819fc71297165ae7fe82bb4db0f5c87f7dbc Mon Sep 17 00:00:00 2001
From: Ryan Joseph <genericptr@gmail.com>
Date: Thu, 29 Nov 2018 08:22:34 +0700
Subject: [PATCH] implicit specialization for generic routines

---
 .gitignore                 |  24 +++
 compiler/globtype.pas      |   6 +-
 compiler/htypechk.pas      | 132 +++++++--------
 compiler/ncal.pas          |  25 ++-
 compiler/pexpr.pas         | 228 +++++++++++++++++++------
 compiler/pgenutil.pas      | 332 +++++++++++++++++++++++++++++--------
 compiler/ppu.pas           |   2 +-
 compiler/symtable.pas      |  21 +++
 tests/test/tgenimspez0.pp  |  27 +++
 tests/test/tgenimspez1.pp  |  37 +++++
 tests/test/tgenimspez10.pp |  15 ++
 tests/test/tgenimspez11.pp |  19 +++
 tests/test/tgenimspez12.pp |  21 +++
 tests/test/tgenimspez2.pp  |  49 ++++++
 tests/test/tgenimspez3.pp  |  25 +++
 tests/test/tgenimspez4.pp  |  26 +++
 tests/test/tgenimspez5.pp  |  16 ++
 tests/test/tgenimspez6.pp  |  49 ++++++
 tests/test/tgenimspez7.pp  |  17 ++
 tests/test/tgenimspez8.pp  |  37 +++++
 tests/test/tgenimspez9.pp  |  76 +++++++++
 21 files changed, 992 insertions(+), 192 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 tests/test/tgenimspez0.pp
 create mode 100644 tests/test/tgenimspez1.pp
 create mode 100644 tests/test/tgenimspez10.pp
 create mode 100644 tests/test/tgenimspez11.pp
 create mode 100644 tests/test/tgenimspez12.pp
 create mode 100644 tests/test/tgenimspez2.pp
 create mode 100644 tests/test/tgenimspez3.pp
 create mode 100644 tests/test/tgenimspez4.pp
 create mode 100644 tests/test/tgenimspez5.pp
 create mode 100644 tests/test/tgenimspez6.pp
 create mode 100644 tests/test/tgenimspez7.pp
 create mode 100644 tests/test/tgenimspez8.pp
 create mode 100644 tests/test/tgenimspez9.pp

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..f22af3de53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# files
+pp
+fpmake
+rtl/darwin/fpcmade.x86_64-darwin
+fpmake_proc1 copy.inc
+tests/*.x86_64-darwin
+rtl/Package.fpc
+tests/createlst
+tests/gparmake
+compiler/ryan*.lpi
+
+# directories
+lazbuild/
+x86_64-darwin/
+tests/tstunits/
+tests/utils
+
+# patterns
+*.app
+*.o
+*.ppu
+*.fpm
+*.rsj
+*.lst
\ No newline at end of file
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index 7d23464d57..5f135c5297 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -446,7 +446,8 @@ interface
          m_isolike_io,          { I/O as it required by an ISO compatible compiler }
          m_isolike_program_para, { program parameters as it required by an ISO compatible compiler }
          m_isolike_mod,         { mod operation as it is required by an iso compatible compiler }
-         m_array_operators      { use Delphi compatible array operators instead of custom ones ("+") }
+         m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
+         m_implicit_generics    { attempt to specialize generic procedure by inferring types from parameters }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -635,7 +636,8 @@ interface
          'ISOIO',
          'ISOPROGRAMPARAS',
          'ISOMOD',
-         'ARRAYOPERATORS'
+         'ARRAYOPERATORS',
+         'IMPLICITGENERICS'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 07c035dc26..9002b8dd26 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -152,22 +152,22 @@ interface
     function token2managementoperator(optoken:ttoken):tmanagementoperator;
 
     { check operator args and result type }
-
-    type
-      toverload_check_flag = (
-        ocf_check_non_overloadable, { also check operators that are (currently) considered as
-                                      not overloadable (e.g. the "+" operator for dynamic arrays
-                                      if modeswitch arrayoperators is active) }
-        ocf_check_only              { only check whether the operator is overloaded, but don't
-                                      modify the passed in node (return true if the operator is
-                                      overloaded, false otherwise) }
-      );
-      toverload_check_flags = set of toverload_check_flag;
-
+
+    type
+      toverload_check_flag = (
+        ocf_check_non_overloadable, { also check operators that are (currently) considered as
+                                      not overloadable (e.g. the "+" operator for dynamic arrays
+                                      if modeswitch arrayoperators is active) }
+        ocf_check_only              { only check whether the operator is overloaded, but don't
+                                      modify the passed in node (return true if the operator is
+                                      overloaded, false otherwise) }
+      );
+      toverload_check_flags = set of toverload_check_flag;
+
     function isbinaryoperatoroverloadable(treetyp:tnodetype;ld:tdef;lt:tnodetype;rd:tdef;rt:tnodetype) : boolean;
     function isoperatoracceptable(pf : tprocdef; optoken : ttoken) : boolean;
-    function isunaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
-    function isbinaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
+    function isunaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
+    function isbinaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
 
     { Register Allocation }
     procedure make_not_regable(p : tnode; how: tregableinfoflags);
@@ -515,9 +515,9 @@ implementation
                     end;
 
                  { <dyn. array> + <dyn. array> is handled by the compiler }
-                 if (m_array_operators in current_settings.modeswitches) and
-                     (treetyp=addn) and
-                     (is_dynamic_array(ld) or is_dynamic_array(rd)) then
+                 if (m_array_operators in current_settings.modeswitches) and
+                     (treetyp=addn) and
+                     (is_dynamic_array(ld) or is_dynamic_array(rd)) then
                     begin
                       allowed:=false;
                       exit;
@@ -720,7 +720,7 @@ implementation
       end;
 
 
-    function isunaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
+    function isunaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
       var
         ld      : tdef;
         optoken : ttoken;
@@ -742,11 +742,11 @@ implementation
         else
           inlinenumber:=in_none;
 
-        if not (ocf_check_non_overloadable in ocf) and not isunaryoperatoroverloadable(t.nodetype,inlinenumber,ld) then
+        if not (ocf_check_non_overloadable in ocf) and not isunaryoperatoroverloadable(t.nodetype,inlinenumber,ld) then
           exit;
 
         { operator overload is possible }
-        result:=not (ocf_check_only in ocf);
+        result:=not (ocf_check_only in ocf);
 
         optoken:=NOTOKEN;
         case t.nodetype of
@@ -766,11 +766,11 @@ implementation
         end;
         if (optoken=NOTOKEN) then
           begin
-            if not (ocf_check_only in ocf) then
-              begin
-                CGMessage(parser_e_operator_not_overloaded);
-                t:=cnothingnode.create;
-              end;
+            if not (ocf_check_only in ocf) then
+              begin
+                CGMessage(parser_e_operator_not_overloaded);
+                t:=cnothingnode.create;
+              end;
             exit;
           end;
 
@@ -790,11 +790,11 @@ implementation
           begin
             candidates.free;
             ppn.free;
-            if not (ocf_check_only in ocf) then
-              begin
-                CGMessage2(parser_e_operator_not_overloaded_2,ld.typename,arraytokeninfo[optoken].str);
-                t:=cnothingnode.create;
-              end;
+            if not (ocf_check_only in ocf) then
+              begin
+                CGMessage2(parser_e_operator_not_overloaded_2,ld.typename,arraytokeninfo[optoken].str);
+                t:=cnothingnode.create;
+              end;
             exit;
           end;
 
@@ -811,16 +811,16 @@ implementation
           begin
             candidates.free;
             ppn.free;
-            if not (ocf_check_only in ocf) then
-              begin
-                CGMessage2(parser_e_operator_not_overloaded_2,ld.typename,arraytokeninfo[optoken].str);
-                t:=cnothingnode.create;
-              end;
+            if not (ocf_check_only in ocf) then
+              begin
+                CGMessage2(parser_e_operator_not_overloaded_2,ld.typename,arraytokeninfo[optoken].str);
+                t:=cnothingnode.create;
+              end;
             exit;
           end;
 
         { Multiple candidates left? }
-        if (cand_cnt>1) and not (ocf_check_only in ocf) then
+        if (cand_cnt>1) and not (ocf_check_only in ocf) then
           begin
             CGMessage(type_e_cant_choose_overload_function);
 {$ifdef EXTDEBUG}
@@ -833,13 +833,13 @@ implementation
           end;
         candidates.free;
 
-        if ocf_check_only in ocf then
-          begin
-            ppn.free;
-            result:=true;
-            exit;
-          end;
-
+        if ocf_check_only in ocf then
+          begin
+            ppn.free;
+            result:=true;
+            exit;
+          end;
+
         addsymref(operpd.procsym);
 
         { the nil as symtable signs firstcalln that this is
@@ -852,7 +852,7 @@ implementation
       end;
 
 
-    function isbinaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
+    function isbinaryoverloaded(var t : tnode;ocf:toverload_check_flags) : boolean;
       var
         rd,ld   : tdef;
         optoken : ttoken;
@@ -945,14 +945,14 @@ implementation
         { load easier access variables }
         ld:=tbinarynode(t).left.resultdef;
         rd:=tbinarynode(t).right.resultdef;
-        if not (ocf_check_non_overloadable in ocf) and
-            not isbinaryoperatoroverloadable(t.nodetype,ld,tbinarynode(t).left.nodetype,rd,tbinarynode(t).right.nodetype) then
+        if not (ocf_check_non_overloadable in ocf) and
+            not isbinaryoperatoroverloadable(t.nodetype,ld,tbinarynode(t).left.nodetype,rd,tbinarynode(t).right.nodetype) then
           exit;
 
         { operator overload is possible }
-        { if we only check for the existance of the overload, then we assume that
-          it is not overloaded }
-        result:=not (ocf_check_only in ocf);
+        { if we only check for the existance of the overload, then we assume that
+          it is not overloaded }
+        result:=not (ocf_check_only in ocf);
 
         case t.nodetype of
            equaln:
@@ -997,19 +997,19 @@ implementation
              optoken:=_OP_IN;
            else
              begin
-               if not (ocf_check_only in ocf) then
-                 begin
-                   CGMessage(parser_e_operator_not_overloaded);
-                   t:=cnothingnode.create;
-                 end;
+               if not (ocf_check_only in ocf) then
+                 begin
+                   CGMessage(parser_e_operator_not_overloaded);
+                   t:=cnothingnode.create;
+                 end;
                exit;
              end;
         end;
 
-        cand_cnt:=search_operator(optoken,(optoken<>_NE) and not (ocf_check_only in ocf));
+        cand_cnt:=search_operator(optoken,(optoken<>_NE) and not (ocf_check_only in ocf));
 
         { no operator found for "<>" then search for "=" operator }
-        if (cand_cnt=0) and (optoken=_NE) and not (ocf_check_only in ocf) then
+        if (cand_cnt=0) and (optoken=_NE) and not (ocf_check_only in ocf) then
           begin
             ppn.free;
             ppn:=nil;
@@ -1021,15 +1021,15 @@ implementation
         if (cand_cnt=0) then
           begin
             ppn.free;
-            if not (ocf_check_only in ocf) then
-              t:=cnothingnode.create;
-            exit;
-          end;
-
-        if ocf_check_only in ocf then
-          begin
-            ppn.free;
-            result:=true;
+            if not (ocf_check_only in ocf) then
+              t:=cnothingnode.create;
+            exit;
+          end;
+
+        if ocf_check_only in ocf then
+          begin
+            ppn.free;
+            result:=true;
             exit;
           end;
 
@@ -3418,7 +3418,7 @@ implementation
         {
           Returns the number of candidates left and the
           first candidate is returned in pdbest
-        }
+        }          
         { Setup the first procdef as best, only count it as a result
           when it is valid }
         bestpd:=FCandidateProcs^.data;
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index 0984293d8b..613421a8c1 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -289,7 +289,7 @@ interface
          dct_propget,
          dct_propput
        );
-
+       
     function reverseparameters(p: tcallparanode): tcallparanode;
     function translate_disp_call(selfnode,parametersnode: tnode; calltype: tdispcalltype; const methodname : ansistring;
       dispid : longint;resultdef : tdef) : tnode;
@@ -309,7 +309,7 @@ interface
 implementation
 
     uses
-      systems,
+      systems,sysutils,
       verbose,globals,fmodule,
       aasmbase,aasmdata,
       symconst,defutil,defcmp,compinnr,
@@ -3472,7 +3472,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
       var
         candidates : tcallcandidates;
@@ -3488,6 +3487,7 @@ implementation
         statements : tstatementnode;
         converted_result_data : ttempcreatenode;
         calltype: tdispcalltype;
+        pdef: tdef;
       begin
          result:=nil;
          candidates:=nil;
@@ -3686,6 +3686,25 @@ implementation
                     end
                    else
                     begin
+                      { failed to find candiates for dummy proc sym so
+                        attempt to implicit specialization and try again }
+                      if (sp_generic_dummy in symtableprocentry.symoptions) and (m_implicit_generics in current_settings.modeswitches) then
+                        begin
+                          if symtableproc.symtabletype in [objectsymtable,recordsymtable] then
+                            pdef:=try_implicit_specialization(symtableprocentry,tabstractrecorddef(symtableproc.defowner),false,left,spezcontext)
+                          else
+                            pdef:=try_implicit_specialization(symtableprocentry,nil,false,left,spezcontext);
+                          if pdef<>generrordef then
+                            begin
+                              candidates.free;
+                              symtableprocentry:=tprocsym(tprocdef(pdef).procsym);
+                              symtableproc:=symtableprocentry.owner;
+                              procdefinition:=nil;
+                              { typecheck again with the new procsym }
+                              result:=pass_typecheck;
+                              exit;
+                            end;
+                        end;
                       { No candidates left, this must be a type error,
                         because wrong size is already checked. procdefinition
                         is filled with the first (random) definition that is
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index bc0606ed4b..fcf02c3a4c 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -53,7 +53,7 @@ interface
     function parse_paras(__colon,__namedpara : boolean;end_of_paras : ttoken) : tnode;
 
     { the ID token has to be consumed before calling this function }
-    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);overload;
 
     function get_intconst:TConstExprInt;
     function get_stringconst:string;
@@ -66,7 +66,7 @@ implementation
 
     uses
        { common }
-       cutils,cclasses,
+       cutils,cclasses,sysutils,
        { global }
        verbose,
        systems,widestr,
@@ -79,7 +79,7 @@ implementation
        nmat,nadd,nmem,nset,ncnv,ninl,ncon,nld,nflw,nbas,nutils,
        { parser }
        scanner,
-       pbase,pinline,ptype,pgenutil,procinfo,cpuinfo
+       pbase,pinline,ptype,pgenutil,procinfo,cpuinfo,htypechk
        ;
 
     function sub_expr(pred_level:Toperator_precedence;flags:texprflags;factornode:tnode):tnode;forward;
@@ -962,14 +962,13 @@ implementation
          end;
       end;
 
-
     { reads the parameter for a subroutine call }
-    procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+    procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;para:tnode;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
       var
          membercall,
          prevafterassn : boolean;
          i        : integer;
-         para,p2  : tnode;
+         p2  : tnode;
          currpara : tparavarsym;
          aprocdef : tprocdef;
       begin
@@ -1060,36 +1059,40 @@ implementation
            end
          else
            begin
-             para:=nil;
-             if anon_inherited then
-              begin
-                if not assigned(current_procinfo) then
-                  internalerror(200305054);
-                for i:=0 to current_procinfo.procdef.paras.count-1 do
+             { parse params if needed }
+             if not assigned(para) then
+               begin
+                 para:=nil;
+                 if anon_inherited then
                   begin
-                    currpara:=tparavarsym(current_procinfo.procdef.paras[i]);
-                    if not(vo_is_hidden_para in currpara.varoptions) then
+                    if not assigned(current_procinfo) then
+                      internalerror(200305054);
+                    for i:=0 to current_procinfo.procdef.paras.count-1 do
                       begin
-                        { inheritance by msgint? }
-                        if assigned(srdef) then
-                          { anonymous inherited via msgid calls only require a var parameter for
-                            both methods, so we need some type casting here }
-                          para:=ccallparanode.create(ctypeconvnode.create_internal(ctypeconvnode.create_internal(
-                            cloadnode.create(currpara,currpara.owner),cformaltype),tparavarsym(tprocdef(srdef).paras[i]).vardef),
-                          para)
-                        else
-                          para:=ccallparanode.create(cloadnode.create(currpara,currpara.owner),para);
-                      end;
-                 end;
-              end
-             else
-              begin
-                if try_to_consume(_LKLAMMER) then
-                 begin
-                   para:=parse_paras(false,false,_RKLAMMER);
-                   consume(_RKLAMMER);
-                 end;
-              end;
+                        currpara:=tparavarsym(current_procinfo.procdef.paras[i]);
+                        if not(vo_is_hidden_para in currpara.varoptions) then
+                          begin
+                            { inheritance by msgint? }
+                            if assigned(srdef) then
+                              { anonymous inherited via msgid calls only require a var parameter for
+                                both methods, so we need some type casting here }
+                              para:=ccallparanode.create(ctypeconvnode.create_internal(ctypeconvnode.create_internal(
+                                cloadnode.create(currpara,currpara.owner),cformaltype),tparavarsym(tprocdef(srdef).paras[i]).vardef),
+                              para)
+                            else
+                              para:=ccallparanode.create(cloadnode.create(currpara,currpara.owner),para);
+                          end;
+                     end;
+                  end
+                 else
+                  begin
+                    if try_to_consume(_LKLAMMER) then
+                     begin
+                       para:=parse_paras(false,false,_RKLAMMER);
+                       consume(_RKLAMMER);
+                     end;
+                  end;
+               end;
              { indicate if this call was generated by a member and
                no explicit self is used, this is needed to determine
                how to handle a destructor call (PFV) }
@@ -1274,9 +1277,9 @@ implementation
          paras.free;
       end;
 
-
     { the ID token has to be consumed before calling this function }
-    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+
+    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;paras:tnode;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);overload;
       var
         isclassref:boolean;
         isrecordtype:boolean;
@@ -1315,7 +1318,7 @@ implementation
               case sym.typ of
                  procsym:
                    begin
-                      do_proc_call(sym,sym.owner,structh,
+                      do_proc_call(sym,sym.owner,structh,paras,
                                    (getaddr and not(token in [_CARET,_POINT])),
                                    again,p1,callflags,spezcontext);
                       { we need to know which procedure is called }
@@ -1418,7 +1421,11 @@ implementation
               end;
            end;
       end;
-
+      
+    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);overload;inline;
+      begin
+        do_member_read(structh,getaddr,sym,nil,p1,again,callflags,spezcontext);
+      end; 
 
     function handle_specialize_inline_specialization(var srsym:tsym;out srsymtable:tsymtable;out spezcontext:tspecializationcontext):boolean;
       var
@@ -1489,6 +1496,40 @@ implementation
             end;
       end;
 
+    { returns true if the symbol is a generic dummy which should
+      attempt to parse parameters and infer specialization }
+    function should_infer_specialization(sym:tsym):boolean;inline;
+      begin
+        result:=(sp_generic_dummy in sym.symoptions) and (sym.typ=typesym) and (m_implicit_generics in current_settings.modeswitches);
+      end;
+
+    { parses parameters and passes results to try_implicit_specialization.
+      this function is used to handle cases where there is only a typesym
+      generic dummy which could infer specialization. }
+    function handle_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_member:boolean;out para: tnode;out spezcontext:tspecializationcontext):tdef;
+      begin
+        { parse paramaters }
+        if try_to_consume(_LKLAMMER) then
+          begin
+            para:=parse_paras(false,false,_RKLAMMER);
+            { inferred specialization requires params }
+            if not assigned(para) then
+              begin
+                result:=generrordef;
+                exit;
+              end;
+            consume(_RKLAMMER);
+          end
+        else
+          { inferred specialization requires params }
+          begin
+            result:=generrordef;
+            exit;
+          end;
+        if assigned(para) then
+          tcallparanode(para).get_paratype;
+        result:=try_implicit_specialization(sym,struct,is_member,para,spezcontext);
+      end;
 
     function handle_factor_typenode(hdef:tdef;getaddr:boolean;var again:boolean;sym:tsym;typeonly:boolean):tnode;
       var
@@ -1498,6 +1539,8 @@ implementation
         isspecialize : boolean;
         spezcontext : tspecializationcontext;
         savedfilepos : tfileposinfo;
+        pdef:tdef;
+        para:tnode;
       begin
          spezcontext:=nil;
          if sym=nil then
@@ -1584,6 +1627,7 @@ implementation
                 else
                   isspecialize:=false;
                 erroroutresult:=true;
+                para:=nil;
                 { TP allows also @TMenu.Load if Load is only }
                 { defined in an anchestor class              }
                 srsym:=search_struct_member(tabstractrecorddef(hdef),pattern);
@@ -1599,6 +1643,15 @@ implementation
                       begin
                         savedfilepos:=current_filepos;
                         consume(_ID);
+                        if should_infer_specialization(srsym) then
+                          begin
+                            pdef:=handle_implicit_specialization(srsym,tabstractrecorddef(hdef),true,para,spezcontext);
+                            if pdef<>generrordef then
+                              begin
+                                srsym:=tprocdef(pdef).procsym;
+                                srsymtable:=srsym.owner;
+                              end;
+                          end;
                         if not (sp_generic_dummy in srsym.symoptions) or
                             not (token in [_LT,_LSHARPBRACKET]) then
                           check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,savedfilepos)
@@ -1616,7 +1669,7 @@ implementation
                   end
                 else
                   if result.nodetype<>specializen then
-                    do_member_read(tabstractrecorddef(hdef),getaddr,srsym,result,again,[],spezcontext);
+                    do_member_read(tabstractrecorddef(hdef),getaddr,srsym,para,result,again,[],spezcontext);
               end;
            end
          else
@@ -1981,6 +2034,8 @@ implementation
      strdef : tdef;
      spezcontext : tspecializationcontext;
      old_current_filepos : tfileposinfo;
+     para : tnode;
+     hdef : tdef;
     label
      skipreckklammercheck,
      skippointdefcheck;
@@ -2333,6 +2388,7 @@ implementation
                        begin
                          erroroutp1:=true;
                          srsym:=nil;
+                         para:=nil;
                          structh:=tabstractrecorddef(p1.resultdef);
                          if isspecialize then
                            begin
@@ -2355,6 +2411,15 @@ implementation
                                begin
                                  old_current_filepos:=current_filepos;
                                  consume(_ID);
+                                 if should_infer_specialization(srsym) then
+                                   begin
+                                     hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                     if hdef <> generrordef then
+                                       begin
+                                         srsym:=tprocdef(hdef).procsym;
+                                         srsymtable:=srsym.owner;
+                                       end;
+                                   end;
                                  if not (sp_generic_dummy in srsym.symoptions) or
                                      not (token in [_LT,_LSHARPBRACKET]) then
                                    check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2376,7 +2441,7 @@ implementation
                            end
                          else
                            if p1.nodetype<>specializen then
-                             do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                             do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                        end
                      else
                      consume(_ID);
@@ -2486,6 +2551,7 @@ implementation
                   classrefdef:
                     begin
                       erroroutp1:=true;
+                      para:=nil;
                       if token=_ID then
                         begin
                           srsym:=nil;
@@ -2511,6 +2577,15 @@ implementation
                                 begin
                                   old_current_filepos:=current_filepos;
                                   consume(_ID);
+                                  if should_infer_specialization(srsym) then
+                                    begin
+                                      hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                      if hdef <> generrordef then
+                                        begin
+                                          srsym:=tprocdef(hdef).procsym;
+                                          srsymtable:=srsym.owner;
+                                        end;
+                                    end;
                                   if not (sp_generic_dummy in srsym.symoptions) or
                                       not (token in [_LT,_LSHARPBRACKET]) then
                                     check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2532,7 +2607,7 @@ implementation
                             end
                           else
                             if p1.nodetype<>specializen then
-                              do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                              do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                         end
                       else { Error }
                         Consume(_ID);
@@ -2543,6 +2618,7 @@ implementation
                         begin
                           erroroutp1:=true;
                           srsym:=nil;
+                          para:=nil;
                           structh:=tobjectdef(p1.resultdef);
                           if isspecialize then
                             begin
@@ -2565,6 +2641,15 @@ implementation
                                 begin
                                    old_current_filepos:=current_filepos;
                                    consume(_ID);
+                                   if should_infer_specialization(srsym) then
+                                     begin
+                                       hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                       if hdef<>generrordef then
+                                         begin
+                                           srsym:=tprocdef(hdef).procsym;
+                                           srsymtable:=srsym.owner;
+                                         end;
+                                     end;
                                    if not (sp_generic_dummy in srsym.symoptions) or
                                        not (token in [_LT,_LSHARPBRACKET]) then
                                      check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2586,7 +2671,7 @@ implementation
                             end
                           else
                             if p1.nodetype<>specializen then
-                              do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                              do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                         end
                       else { Error }
                         Consume(_ID);
@@ -2601,7 +2686,7 @@ implementation
                           if search_objc_method(pattern,srsym,srsymtable) then
                             begin
                               consume(_ID);
-                              do_proc_call(srsym,srsymtable,nil,
+                              do_proc_call(srsym,srsymtable,nil,nil,
                                 (getaddr and not(token in [_CARET,_POINT])),
                                 again,p1,[cnf_objc_id_call],nil);
                               { we need to know which procedure is called }
@@ -2771,6 +2856,9 @@ implementation
            dummypos,
            tokenpos: tfileposinfo;
            spezcontext : tspecializationcontext;
+           genname : string;
+           origsym : tsym;
+           para: tnode;
          begin
            { allow post fix operators }
            again:=true;
@@ -2779,6 +2867,7 @@ implementation
            tokenpos:=current_filepos;
            p1:=nil;
            spezcontext:=nil;
+           para:=nil;
 
            { avoid warning }
            fillchar(dummypos,sizeof(dummypos),0);
@@ -2919,14 +3008,44 @@ implementation
                      )
                    ) then
                  begin
-                   srsym:=resolve_generic_dummysym(srsym.name);
-                   if assigned(srsym) then
-                     srsymtable:=srsym.owner
-                   else
-                     begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
-                     end;
+                  if should_infer_specialization(srsym) then
+                    begin
+                      hdef:=handle_implicit_specialization(srsym,nil,false,para,spezcontext);
+                      if hdef<>generrordef then
+                        begin
+                          srsym:=tprocdef(hdef).procsym;
+                          srsymtable:=srsym.owner;
+                        end
+                      else
+                        begin
+                          srsym:=resolve_generic_dummysym(srsym.name);
+                          if assigned(srsym) then
+                            srsymtable:=srsym.owner
+                          else
+                            begin
+                              srsymtable:=nil;
+                              wasgenericdummy:=true;
+                            end;
+                        end;
+                    end
+                  else
+                    begin
+                      origsym:=srsym;
+                      srsym:=resolve_generic_dummysym(srsym.name);
+                      if assigned(srsym) then
+                        srsymtable:=srsym.owner
+                      else
+                        begin
+                          { the dummy could not be resolved so call node
+                            will attempt to infer specialization during
+                            type checking }
+                          if m_implicit_generics in current_settings.modeswitches then
+                            srsym:=origsym
+                          else
+                            srsymtable:=nil;
+                          wasgenericdummy:=true;
+                        end;
+                    end;
                  end;
 
                { check hints, but only if it isn't a potential generic symbol;
@@ -3188,9 +3307,10 @@ implementation
                           tmpgetaddr:=getaddr and not(token in [_POINT,_LECKKLAMMER])
                         else
                           tmpgetaddr:=getaddr and not(token in [_CARET,_POINT,_LECKKLAMMER]);
-                        do_proc_call(srsym,srsymtable,nil,tmpgetaddr,
+                        do_proc_call(srsym,srsymtable,nil,para,tmpgetaddr,
                                      again,p1,callflags,spezcontext);
                         spezcontext:=nil;
+                        para:=nil;
                       end;
                   end;
 
@@ -3593,7 +3713,7 @@ implementation
                                  (srsym.typ<>procsym) then
                                 internalerror(200303171);
                               p1:=nil;
-                              do_proc_call(srsym,srsym.owner,hclassdef,false,again,p1,[],nil);
+                              do_proc_call(srsym,srsym.owner,hclassdef,nil,false,again,p1,[],nil);
                             end
                           else
                             begin
@@ -4147,7 +4267,7 @@ implementation
                   else
                     begin
                       { regular procedure/function call }
-                      do_proc_call(gensym,gensym.owner,nil,
+                      do_proc_call(gensym,gensym.owner,nil,nil,
                                    (getaddr and not(token in [_CARET,_POINT,_LECKKLAMMER])),
                                    again,result,[],spezcontext);
                       spezcontext:=nil;
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 7760a4e134..01c9af50da 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,9 +33,12 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
-
+  
+    function try_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_struct_member:boolean;para: tnode;out spezcontext:tspecializationcontext):tdef;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string;parsedtype:tdef;symname:string;parsedpos:tfileposinfo);inline;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string);inline;
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;inline;
@@ -65,17 +68,98 @@ uses
   { common }
   cutils,fpccrc,
   { global }
-  globals,tokens,verbose,finput,
+  sysutils,globals,tokens,verbose,finput,
   { symtable }
   symconst,symsym,symtable,defcmp,procinfo,
   { modules }
   fmodule,
-  node,nobj,
+  nobj,ncal,
+  htypechk,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub;
 
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;out prettyname,specializename:ansistring);
+      var
+        namepart : string;
+        prettynamepart : ansistring;
+        module : tmodule;
+      begin
+        prettyname:='';
+        specializename:='';
+        module:=find_module_from_symtable(paramtype.owner);
+        if not assigned(module) then
+          internalerror(2016112802);
+        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+paramtype.unique_id_str;
+        { we use the full name of the type to uniquely identify it }
+        if (symtablestack.top.symtabletype=parasymtable) and
+            (symtablestack.top.defowner.typ=procdef) and
+            (paramtype.owner=symtablestack.top) then
+          begin
+            { special handling for specializations inside generic function declarations }
+            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
+          end
+        else
+          begin
+            prettynamepart:=paramtype.fullownerhierarchyname(true);
+          end;
+        specializename:=specializename+namepart;
+        if not first then
+          prettyname:=prettyname+',';
+        prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
+    procedure make_genname(def_list_count:integer;symname:string;genericdef:tdef;out genname,ugenname:string);
+      var
+        countstr: string;
+        gencount,
+        i: integer;
+      begin
+        { use the name of the symbol as procvars return a user friendly version
+          of the name }
+        if symname='' then
+          genname:=ttypesym(genericdef.typesym).realname
+        else
+          genname:=symname;
+
+        { in case of non-Delphi mode the type name could already be a generic
+          def (but maybe the wrong one) }
+        if assigned(genericdef) and
+            ([df_generic,df_specialization]*genericdef.defoptions<>[]) then
+          begin
+            { remove the type count suffix from the generic's name }
+            for i:=Length(genname) downto 1 do
+              if genname[i]='$' then
+                begin
+                  genname:=copy(genname,1,i-1);
+                  break;
+                end;
+            { in case of a specialization we've only reached the specialization
+              checksum yet }
+            if df_specialization in genericdef.defoptions then
+              for i:=length(genname) downto 1 do
+                if genname[i]='$' then
+                  begin
+                    genname:=copy(genname,1,i-1);
+                    break;
+                  end;
+          end
+        else
+          begin
+            split_generic_name(genname,ugenname,gencount);
+            if genname<>ugenname then
+              genname:=ugenname;
+          end;
+
+        { search a generic with the given count of params }
+        countstr:='';
+        str(def_list_count,countstr);
+
+        genname:=genname+'$'+countstr;
+        ugenname:=upper(genname);
+      end;
+
     procedure maybe_add_waiting_unit(tt:tdef);
       var
         hmodule : tmodule;
@@ -372,26 +456,7 @@ uses
                     else if (typeparam.resultdef.typ<>errordef) then
                       begin
                         genericdeflist.Add(typeparam.resultdef);
-                        module:=find_module_from_symtable(typeparam.resultdef.owner);
-                        if not assigned(module) then
-                          internalerror(2016112802);
-                        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+typeparam.resultdef.unique_id_str;
-                        { we use the full name of the type to uniquely identify it }
-                        if (symtablestack.top.symtabletype=parasymtable) and
-                            (symtablestack.top.defowner.typ=procdef) and
-                            (typeparam.resultdef.owner=symtablestack.top) then
-                          begin
-                            { special handling for specializations inside generic function declarations }
-                            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
-                          end
-                        else
-                          begin
-                            prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true);
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,prettyname,specializename);
                       end;
                   end
                 else
@@ -428,6 +493,183 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function generate_implicit_specialization(out context:tspecializationcontext;symname:string;struct:tabstractrecorddef;is_struct_member:boolean;paramtypes:tfplist;paracount:integer):tdef;
+      var
+        parsedpos:tfileposinfo;
+        poslist:tfplist;
+        found: boolean;
+        i: longint;
+        ugenname: string;
+        paramtype: tdef;
+        parampos : pfileposinfo;
+        tmpparampos : tfileposinfo;
+      begin
+        result:=nil;        
+        context:=tspecializationcontext.create;
+        fillchar(parsedpos,sizeof(parsedpos),0);
+        poslist:=context.poslist;
+        tmpparampos:=current_filepos;
+
+        { parse_generic_specialization_types_internal }
+        for i := 0 to min(paramtypes.count,paracount)-1 do
+          begin
+            paramtype:=tdef(paramtypes[i]);
+            if assigned(poslist) then
+              begin
+                new(parampos);
+                parampos^:=tmpparampos;
+                poslist.add(parampos);
+              end;
+            context.genericdeflist.Add(paramtype);
+            make_prettystring(paramtype,true,context.prettyname,context.specializename);
+          end;
+
+        { generate_specialization_phase1 }
+        make_genname(context.genericdeflist.Count,symname,nil,context.genname,ugenname);
+
+        if assigned(struct) then
+          found:=searchsym_in_struct(struct,is_struct_member,ugenname,context.sym,context.symtable,[ssf_search_helper])
+        else
+          found:=searchsym(ugenname,context.sym,context.symtable);
+
+        if not found or not (context.sym.typ in [typesym,procsym]) then
+          begin
+            context.free;
+            context:=nil;
+            result:=generrordef;
+            exit;
+          end;
+
+        { we've found the correct def }
+        if context.sym.typ=typesym then
+          result:=tstoreddef(ttypesym(context.sym).typedef)
+        else
+          begin
+            if tprocsym(context.sym).procdeflist.count=0 then
+              internalerror(2015061203);
+            result:=tstoreddef(tprocsym(context.sym).procdefList[0]);
+          end;
+      end;
+
+    function try_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_struct_member:boolean;para: tnode;out spezcontext:tspecializationcontext):tdef;
+      { insert def to front of list (params are processed in reverse order)
+        and only if the param is unique. }
+      procedure add_unique_param(list:tfplist;def:tdef);inline;
+        begin
+          if list.indexof(def)=-1 then
+            list.insert(0,def);
+        end;
+      var
+        i, p: integer;
+        srsym: tsym;
+        srsymtable: TSymtable;
+        ignorevisibility,
+        allowdefaultparas,
+        objcidcall,
+        explicitunit,
+        searchhelpers,
+        anoninherited: boolean;
+        count: integer;
+        bestpd: tabstractprocdef;
+        genname: string;
+        candidates: tcallcandidates;
+        pt: tcallparanode;
+        paraindex: integer;
+        paramtypes: tfplist;
+        paradef: tdef;
+        typename: string;
+        newtype: ttypesym;
+      begin
+        result:=nil;
+        paramtypes:=nil;
+        candidates:=nil;
+        { count params }
+        paraindex:=0;
+        pt:=tcallparanode(para);
+        while assigned(pt) do
+          begin
+            pt:=tcallparanode(pt.nextpara);
+            paraindex:=paraindex+1;
+          end;
+        { find generic procsyms by param count, starting from
+          number of parsed params. if a procsym is found then
+          validate via tcallcandidates and build list of *unique*
+          types for use when specializing.
+        
+          inferred generic types are evaluated by inserting
+          non-repating types into the list in linear order.
+
+            (1,'string') = <Integer,String>
+            (1,2,3,4,5,6) = <Integer>
+            ('a','b') = <String>
+            ('string',1) = <String,Integer>
+            ('a',1,'b',2,'c') = <String,Integer>
+        }
+        for i:=paraindex downto 1 do
+          begin
+            genname:=sym.name+'$'+tostr(i);
+            if assigned(struct) then
+              searchsym_in_struct(struct,is_struct_member,genname,srsym,srsymtable,[ssf_search_helper])
+            else
+              searchsym(genname,srsym,srsymtable);
+            if assigned(srsym) then
+              begin
+                ignorevisibility:=false;
+                allowdefaultparas:=true;
+                objcidcall:=false;
+                explicitunit:=false;
+                searchhelpers:=false;
+                anoninherited:=false;
+                spezcontext:=nil;
+                candidates:=tcallcandidates.create(tprocsym(srsym),srsym.owner,para,ignorevisibility,
+                  allowdefaultparas,objcidcall,explicitunit,
+                  searchhelpers,anoninherited,spezcontext);
+                if candidates.count>0 then
+                  begin
+                    candidates.get_information;
+                    count:=candidates.choose_best(bestpd,false);
+                    if count>0 then
+                      begin
+                        if not assigned(paramtypes) then
+                          paramtypes:=tfplist.create;
+                        pt:=tcallparanode(para);
+                        paraindex:=0;
+                        while assigned(pt) do
+                          begin
+                            paradef:=pt.paravalue.resultdef;
+                            { there is no typesym for the def so create a temporary one }
+                            if paradef.typesym=nil then
+                              begin
+                                typename:='$'+srsym.realname+'$'+hexstr(paradef);
+                                newtype:=ctypesym.create(typename,paradef,false);
+                                newtype.owner:=paradef.owner;
+                                { TODO: newtype is never released
+                                  we could release it in the spezcontext but
+                                  I don't see where this is released either }
+                              end;
+                            if paradef.typesym=nil then
+                              internalerror(2019022602);
+                            add_unique_param(paramtypes,paradef);
+                            pt:=tcallparanode(pt.nextpara);
+                            paraindex:=paraindex+1;
+                          end;
+                        if paramtypes.count>0 then
+                          begin
+                            result:=generate_implicit_specialization(spezcontext,sym.realname,struct,is_struct_member,paramtypes,i);
+                            paramtypes.clear;
+                            if result<>generrordef then
+                              break;
+                          end;
+                      end;
+                  end;
+                freeandnil(candidates);
+              end;
+          end;
+        freeandnil(paramtypes);
+        freeandnil(candidates);
+        if result=nil then
+          result:=generrordef;
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
@@ -589,49 +831,7 @@ uses
             exit;
           end;
 
-        { use the name of the symbol as procvars return a user friendly version
-          of the name }
-        if symname='' then
-          genname:=ttypesym(genericdef.typesym).realname
-        else
-          genname:=symname;
-
-        { in case of non-Delphi mode the type name could already be a generic
-          def (but maybe the wrong one) }
-        if assigned(genericdef) and
-            ([df_generic,df_specialization]*genericdef.defoptions<>[]) then
-          begin
-            { remove the type count suffix from the generic's name }
-            for i:=Length(genname) downto 1 do
-              if genname[i]='$' then
-                begin
-                  genname:=copy(genname,1,i-1);
-                  break;
-                end;
-            { in case of a specialization we've only reached the specialization
-              checksum yet }
-            if df_specialization in genericdef.defoptions then
-              for i:=length(genname) downto 1 do
-                if genname[i]='$' then
-                  begin
-                    genname:=copy(genname,1,i-1);
-                    break;
-                  end;
-          end
-        else
-          begin
-            split_generic_name(genname,ugenname,gencount);
-            if genname<>ugenname then
-              genname:=ugenname;
-          end;
-
-        { search a generic with the given count of params }
-        countstr:='';
-        str(context.genericdeflist.Count,countstr);
-
-        genname:=genname+'$'+countstr;
-        ugenname:=upper(genname);
-
+        make_genname(context.genericdeflist.Count,symname,genericdef,genname,ugenname);
         context.genname:=genname;
 
         if assigned(genericdef) and (genericdef.owner.symtabletype in [objectsymtable,recordsymtable]) then
diff --git a/compiler/ppu.pas b/compiler/ppu.pas
index 10c42e7eb8..31011be3e8 100644
--- a/compiler/ppu.pas
+++ b/compiler/ppu.pas
@@ -43,7 +43,7 @@ type
 {$endif Test_Double_checksum}
 
 const
-  CurrentPPUVersion = 201;
+  CurrentPPUVersion = 203;
 
 { unit flags }
   uf_init                = $000001; { unit has initialization section }
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 796b2d6736..893ae64d27 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -340,6 +340,7 @@ interface
     function  searchsym_in_named_module(const unitname, symname: TIDString; out srsym: tsym; out srsymtable: tsymtable): boolean;
     function  searchsym_in_class(classh: tobjectdef; contextclassh:tabstractrecorddef;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;
     function  searchsym_in_record(recordh:tabstractrecorddef;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable):boolean;
+    function  searchsym_in_struct(structh:tabstractrecorddef;ismember:boolean;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;inline;
     function  searchsym_in_class_by_msgint(classh:tobjectdef;msgid:longint;out srdef : tdef;out srsym:tsym;out srsymtable:TSymtable):boolean;
     function  searchsym_in_class_by_msgstr(classh:tobjectdef;const s:string;out srsym:tsym;out srsymtable:TSymtable):boolean;
     { searches symbols inside of a helper's implementation }
@@ -3621,6 +3622,26 @@ implementation
         srsymtable:=nil;
       end;
 
+    function  searchsym_in_struct(structh:tabstractrecorddef;ismember:boolean;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;
+      begin
+        if structh.typ=recorddef then
+          begin
+            if ismember then
+              begin
+                srsym:=search_struct_member(structh,s);
+                if assigned(srsym) then
+                  srsymtable:=srsym.owner;
+                result := assigned(srsym);
+              end
+            else
+              result:=searchsym_in_record(structh,s,srsym,srsymtable);
+          end
+        else if structh.typ=objectdef then
+          result:=searchsym_in_class(tobjectdef(structh),tobjectdef(structh),s,srsym,srsymtable,flags)
+        else
+          internalerror(2019030203);
+      end;
+
     function searchsym_in_class_by_msgint(classh:tobjectdef;msgid:longint;out srdef : tdef;out srsym:tsym;out srsymtable:TSymtable):boolean;
       var
         def : tdef;
diff --git a/tests/test/tgenimspez0.pp b/tests/test/tgenimspez0.pp
new file mode 100644
index 0000000000..85e34a39e1
--- /dev/null
+++ b/tests/test/tgenimspez0.pp
@@ -0,0 +1,27 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez0;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis<T>',sizeof(msg));
+end;
+
+var
+	a0: array of byte;
+	a1: set of char;
+	a2: record i: integer end;
+const
+	c0 = 'string';
+	c1 = [1,2,3];
+	c2 = nil;
+begin
+	DoThis(a0);
+	DoThis(a1);
+	DoThis(a2);
+
+	DoThis(c0);
+	DoThis(c1);
+	DoThis(c2);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez1.pp b/tests/test/tgenimspez1.pp
new file mode 100644
index 0000000000..6edb9a0cbb
--- /dev/null
+++ b/tests/test/tgenimspez1.pp
@@ -0,0 +1,37 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez1;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+begin
+	DoThis(1);											// DoThis$1#1:1
+	DoThis(1, 1);										// DoThis$1#2:1 1
+	DoThis('a', 'a');								// DoThis$1#2:a a
+	DoThis('a', 1);									// DoThis$2#2:a 1
+	DoThis('a', 1, TObject.Create);	// DoThis$2#3:a 1 TObject
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez10.pp b/tests/test/tgenimspez10.pp
new file mode 100644
index 0000000000..2b467821a6
--- /dev/null
+++ b/tests/test/tgenimspez10.pp
@@ -0,0 +1,15 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez10;
+
+generic procedure ClearMem<T: record>(var r: T);
+begin
+	FillChar(r, sizeof(T), 0);
+end;
+
+var
+	r: record i: integer end;
+begin
+	ClearMem(r);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez11.pp b/tests/test/tgenimspez11.pp
new file mode 100644
index 0000000000..c37c01aa55
--- /dev/null
+++ b/tests/test/tgenimspez11.pp
@@ -0,0 +1,19 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez11;
+
+procedure DoThis(msg: integer); overload;
+begin
+	writeln('DoThis:',msg);
+end;
+
+generic procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1:',msg);
+end;
+
+begin
+	DoThis(1);
+	DoThis('aaa');
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez12.pp b/tests/test/tgenimspez12.pp
new file mode 100644
index 0000000000..7a88bbd9f8
--- /dev/null
+++ b/tests/test/tgenimspez12.pp
@@ -0,0 +1,21 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez12;
+
+generic procedure DoThis<T,U>(param1: T; param2: U);
+begin
+	writeln('DoThis<T,U>',sizeof(param1),' ',sizeof(param2));
+end;
+
+generic procedure DoThis<T>(msg: T);
+var
+	i: T;
+begin
+	writeln('DoThis<T>',sizeof(msg), ' ', i);
+	DoThis(i,[1,2,3]);
+end;
+
+begin
+	DoThis('string');
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez2.pp b/tests/test/tgenimspez2.pp
new file mode 100644
index 0000000000..6bafd14fb3
--- /dev/null
+++ b/tests/test/tgenimspez2.pp
@@ -0,0 +1,49 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez2;
+
+type
+	TMyObject = class
+		generic procedure DoThis<T>(msg: T);
+		generic procedure DoThis<T>(msg: T; param1:T);
+		generic procedure DoThis<T,U>(msg: T);
+		generic procedure DoThis<T,U>(msg: T; param1: U);
+		generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+	end;
+
+generic procedure TMyObject.DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure TMyObject.DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+var
+	obj: TMyObject;
+begin
+	obj := TMyObject.Create;
+	obj.DoThis(1);
+	obj.DoThis('hello');
+	obj.DoThis(1, 1);
+	obj.DoThis('hello', 'hello');
+	obj.DoThis('hello', 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez3.pp b/tests/test/tgenimspez3.pp
new file mode 100644
index 0000000000..eb4b9a450f
--- /dev/null
+++ b/tests/test/tgenimspez3.pp
@@ -0,0 +1,25 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez3;
+
+type
+	TMyObject = class
+		generic class procedure DoThis<T>(param1: T);
+		generic class procedure DoThis<T,U>(param1: T; param2: U);
+	end;
+
+generic class procedure TMyObject.DoThis<T>(param1: T);
+begin
+	writeln('DoThis$1#1:', param1);
+end;
+
+generic class procedure TMyObject.DoThis<T,U>(param1: T; param2: U);
+begin
+	writeln('DoThis$2#2:', param1, ',', param2);
+end;
+
+begin
+	TMyObject.DoThis(10);
+	TMyObject.DoThis(10, 'hello');
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez4.pp b/tests/test/tgenimspez4.pp
new file mode 100644
index 0000000000..94bd58c15a
--- /dev/null
+++ b/tests/test/tgenimspez4.pp
@@ -0,0 +1,26 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez4;
+
+var
+	Res: integer = 0;
+
+procedure DoThis(msg: integer); overload;
+begin
+	writeln('DoThis:',msg);
+	Res := 1;
+end;
+
+generic procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1:',msg);
+	Res := 2;
+end;
+
+begin
+	DoThis(1);
+	// non-generic function takes precedence so Res must be 1
+	if Res <> 1 then
+		Halt(1);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez5.pp b/tests/test/tgenimspez5.pp
new file mode 100644
index 0000000000..281ff09045
--- /dev/null
+++ b/tests/test/tgenimspez5.pp
@@ -0,0 +1,16 @@
+{%FAIL}
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez5;
+
+generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+end;
+
+begin
+	DoThis('aa', 'aa', TObject.Create);
+	// wil be specialized as DoThis(msg: integer; param1: TObject; param2: TObject)
+	// so we expect an incompatible type error
+	DoThis(1, 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez6.pp b/tests/test/tgenimspez6.pp
new file mode 100644
index 0000000000..63c96e03b3
--- /dev/null
+++ b/tests/test/tgenimspez6.pp
@@ -0,0 +1,49 @@
+{$mode objfpc}
+{$modeswitch advancedrecords}
+{$modeswitch implicitgenerics}
+
+program tgenimspez6;
+
+type
+	TMyRecord = record
+		generic procedure DoThis<T>(msg: T);
+		generic procedure DoThis<T>(msg: T; param1:T);
+		generic procedure DoThis<T,U>(msg: T);
+		generic procedure DoThis<T,U>(msg: T; param1: U);
+		generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+	end;
+
+generic procedure TMyRecord.DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure TMyRecord.DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+var
+	obj: TMyRecord;
+begin
+	obj.DoThis(1);
+	obj.DoThis('hello');
+	obj.DoThis(1, 1);
+	obj.DoThis('hello', 'hello');
+	obj.DoThis('hello', 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez7.pp b/tests/test/tgenimspez7.pp
new file mode 100644
index 0000000000..783050bf62
--- /dev/null
+++ b/tests/test/tgenimspez7.pp
@@ -0,0 +1,17 @@
+{%FAIL}
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez7;
+
+type
+	TMyObject = class
+	end;
+
+generic procedure DoThis<T: TMyObject>(obj: T);
+begin
+end;
+
+begin
+	DoThis(TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez8.pp b/tests/test/tgenimspez8.pp
new file mode 100644
index 0000000000..ac4d7bb88e
--- /dev/null
+++ b/tests/test/tgenimspez8.pp
@@ -0,0 +1,37 @@
+{$mode delphi}
+{$modeswitch implicitgenerics}
+
+program tgenimspez8;
+
+procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+procedure DoThis<T>(msg: T; param1: T); overload;
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+procedure DoThis<T,U>(msg: T); overload;
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+procedure DoThis<T,U>(msg: T; param1: U); overload;
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+procedure DoThis<T,U>(msg: T; param1: U; param2: TObject); overload;
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+begin
+	DoThis(1);											// DoThis$1#1:1
+	DoThis(1, 1);										// DoThis$1#2:1 1
+	DoThis('a', 'a');								// DoThis$1#2:a a
+	DoThis('a', 1);									// DoThis$2#2:a 1
+	DoThis('a', 1, TObject.Create);	// DoThis$2#3:a 1 TObject
+end.
\ No newline at end of file
diff --git a/tests/test/tgenimspez9.pp b/tests/test/tgenimspez9.pp
new file mode 100644
index 0000000000..3c1524d05c
--- /dev/null
+++ b/tests/test/tgenimspez9.pp
@@ -0,0 +1,76 @@
+{$mode objfpc}
+{$modeswitch implicitgenerics}
+
+program tgenimspez9;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+var
+	s1: string;
+	s2: ansistring;
+	s3: widestring;
+	s4: unicodestring;
+	s5: pchar;
+	s6: ansichar;
+	s7: char;
+	s8: widechar;
+	s9: unicodechar;
+
+	i1: byte;
+	i2: shortint;
+	i3: smallint;
+	i4: word;
+	i5: integer;
+	i6: cardinal;
+	i7: longint;
+	i8: longword;
+	i9: int64;
+	i10: qword;
+
+	r1: Real;
+	r2: single;
+	r3: double;
+	r4: extended;
+	r5: comp;
+	r6: currency;
+
+	b1: boolean;
+	b2: bytebool;
+	b3: wordbool;
+	b4: longbool;
+begin
+	DoThis(s1);
+	DoThis(s2);
+	DoThis(s3);
+	DoThis(s4);
+	DoThis(s5);
+	DoThis(s6);
+	DoThis(s7);
+	DoThis(s8);
+
+	DoThis(i1);
+	DoThis(i2);
+	DoThis(i3);
+	DoThis(i4);
+	DoThis(i5);
+	DoThis(i6);
+	DoThis(i7);
+	DoThis(i8);
+	DoThis(i9);
+	DoThis(i10);
+
+	DoThis(r1);
+	DoThis(r2);
+	DoThis(r3);
+	DoThis(r4);
+	DoThis(r5);
+	DoThis(r6);
+
+	DoThis(b1);
+	DoThis(b2);
+	DoThis(b3);
+	DoThis(b4);
+end.
\ No newline at end of file
-- 
2.17.2 (Apple Git-113)

gen-implicit.diff (65,338 bytes)   

Florian

2019-03-24 11:04

administrator   ~0115013

I had only a brief look:
- I would name the mode switch differently: implicitfunctionspecialization? long but clear
- do not use tabs
- avoid changes like just adding spaces to a line (line 550/551 in the patch)

Did you do full regression testing (make full in fpc/tests)?

Ryan Joseph

2019-03-24 14:56

reporter   ~0115020

I'll ask the list about switch name ideas they may have. Something broke in my system and I'm not able to run the tests right now but I'll double check that. I'm pretty certain I did this before and didn't see any problems.

Where did you see tabs? I'll pull them out but I'm not sure where to look.

Ryan Joseph

2019-03-24 16:54

reporter   ~0115022

Got the tests to run again and found some regressions I need to track down and fix now. Updating pending.

Ryan Joseph

2019-03-25 15:00

reporter  

patch_3_25.diff (62,704 bytes)   
From 94bf754383f53315f35b985e407d0bb1abc02808 Mon Sep 17 00:00:00 2001
From: Ryan Joseph <genericptr@gmail.com>
Date: Thu, 29 Nov 2018 08:22:34 +0700
Subject: [PATCH] implicit function specialization

---
 .gitignore                   |  24 +++
 compiler/globtype.pas        |   8 +-
 compiler/ncal.pas            |  25 ++-
 compiler/pexpr.pas           | 240 ++++++++++++++++++-------
 compiler/pgenutil.pas        | 330 ++++++++++++++++++++++++++++-------
 compiler/pinline.pas         |   8 +-
 compiler/ppu.pas             |   2 +-
 compiler/symtable.pas        |  21 +++
 tests/test/timpfuncspez1.pp  |  27 +++
 tests/test/timpfuncspez10.pp |  76 ++++++++
 tests/test/timpfuncspez11.pp |  15 ++
 tests/test/timpfuncspez12.pp |  19 ++
 tests/test/timpfuncspez13.pp |  21 +++
 tests/test/timpfuncspez2.pp  |  37 ++++
 tests/test/timpfuncspez3.pp  |  49 ++++++
 tests/test/timpfuncspez4.pp  |  25 +++
 tests/test/timpfuncspez5.pp  |  26 +++
 tests/test/timpfuncspez6.pp  |  16 ++
 tests/test/timpfuncspez7.pp  |  49 ++++++
 tests/test/timpfuncspez8.pp  |  17 ++
 tests/test/timpfuncspez9.pp  |  37 ++++
 21 files changed, 931 insertions(+), 141 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 tests/test/timpfuncspez1.pp
 create mode 100644 tests/test/timpfuncspez10.pp
 create mode 100644 tests/test/timpfuncspez11.pp
 create mode 100644 tests/test/timpfuncspez12.pp
 create mode 100644 tests/test/timpfuncspez13.pp
 create mode 100644 tests/test/timpfuncspez2.pp
 create mode 100644 tests/test/timpfuncspez3.pp
 create mode 100644 tests/test/timpfuncspez4.pp
 create mode 100644 tests/test/timpfuncspez5.pp
 create mode 100644 tests/test/timpfuncspez6.pp
 create mode 100644 tests/test/timpfuncspez7.pp
 create mode 100644 tests/test/timpfuncspez8.pp
 create mode 100644 tests/test/timpfuncspez9.pp

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..f22af3de53
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# files
+pp
+fpmake
+rtl/darwin/fpcmade.x86_64-darwin
+fpmake_proc1 copy.inc
+tests/*.x86_64-darwin
+rtl/Package.fpc
+tests/createlst
+tests/gparmake
+compiler/ryan*.lpi
+
+# directories
+lazbuild/
+x86_64-darwin/
+tests/tstunits/
+tests/utils
+
+# patterns
+*.app
+*.o
+*.ppu
+*.fpm
+*.rsj
+*.lst
\ No newline at end of file
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index 7d23464d57..5b48e9e495 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -446,7 +446,8 @@ interface
          m_isolike_io,          { I/O as it required by an ISO compatible compiler }
          m_isolike_program_para, { program parameters as it required by an ISO compatible compiler }
          m_isolike_mod,         { mod operation as it is required by an iso compatible compiler }
-         m_array_operators      { use Delphi compatible array operators instead of custom ones ("+") }
+         m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
+         m_implicit_function_specialization    { attempt to specialize generic function by inferring types from parameters }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -597,7 +598,7 @@ interface
 
        cstylearrayofconst = [pocall_cdecl,pocall_cppdecl,pocall_mwpascal,pocall_sysv_abi_cdecl,pocall_ms_abi_cdecl];
 
-       modeswitchstr : array[tmodeswitch] of string[18] = ('',
+       modeswitchstr : array[tmodeswitch] of string[30] = ('',
          '','','','','','','',
          {$ifdef gpc_mode}'',{$endif}
          { more specific }
@@ -635,7 +636,8 @@ interface
          'ISOIO',
          'ISOPROGRAMPARAS',
          'ISOMOD',
-         'ARRAYOPERATORS'
+         'ARRAYOPERATORS',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index 0984293d8b..cb4f827553 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -289,7 +289,7 @@ interface
          dct_propget,
          dct_propput
        );
-
+       
     function reverseparameters(p: tcallparanode): tcallparanode;
     function translate_disp_call(selfnode,parametersnode: tnode; calltype: tdispcalltype; const methodname : ansistring;
       dispid : longint;resultdef : tdef) : tnode;
@@ -309,7 +309,7 @@ interface
 implementation
 
     uses
-      systems,
+      systems,sysutils,
       verbose,globals,fmodule,
       aasmbase,aasmdata,
       symconst,defutil,defcmp,compinnr,
@@ -3472,7 +3472,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
       var
         candidates : tcallcandidates;
@@ -3488,6 +3487,7 @@ implementation
         statements : tstatementnode;
         converted_result_data : ttempcreatenode;
         calltype: tdispcalltype;
+        pdef: tdef;
       begin
          result:=nil;
          candidates:=nil;
@@ -3686,6 +3686,25 @@ implementation
                     end
                    else
                     begin
+                      { failed to find candiates for dummy proc sym so
+                        attempt to implicit specialization and try again }
+                      if (sp_generic_dummy in symtableprocentry.symoptions) and (m_implicit_function_specialization in current_settings.modeswitches) then
+                        begin
+                          if symtableproc.symtabletype in [objectsymtable,recordsymtable] then
+                            pdef:=try_implicit_specialization(symtableprocentry,tabstractrecorddef(symtableproc.defowner),false,left,spezcontext)
+                          else
+                            pdef:=try_implicit_specialization(symtableprocentry,nil,false,left,spezcontext);
+                          if pdef<>generrordef then
+                            begin
+                              candidates.free;
+                              symtableprocentry:=tprocsym(tprocdef(pdef).procsym);
+                              symtableproc:=symtableprocentry.owner;
+                              procdefinition:=nil;
+                              { typecheck again with the new procsym }
+                              result:=pass_typecheck;
+                              exit;
+                            end;
+                        end;
                       { No candidates left, this must be a type error,
                         because wrong size is already checked. procdefinition
                         is filled with the first (random) definition that is
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index bc0606ed4b..878f484d2f 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -53,7 +53,7 @@ interface
     function parse_paras(__colon,__namedpara : boolean;end_of_paras : ttoken) : tnode;
 
     { the ID token has to be consumed before calling this function }
-    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;paras:tnode;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
 
     function get_intconst:TConstExprInt;
     function get_stringconst:string;
@@ -66,7 +66,7 @@ implementation
 
     uses
        { common }
-       cutils,cclasses,
+       cutils,cclasses,sysutils,
        { global }
        verbose,
        systems,widestr,
@@ -79,7 +79,7 @@ implementation
        nmat,nadd,nmem,nset,ncnv,ninl,ncon,nld,nflw,nbas,nutils,
        { parser }
        scanner,
-       pbase,pinline,ptype,pgenutil,procinfo,cpuinfo
+       pbase,pinline,ptype,pgenutil,procinfo,cpuinfo,htypechk
        ;
 
     function sub_expr(pred_level:Toperator_precedence;flags:texprflags;factornode:tnode):tnode;forward;
@@ -962,14 +962,13 @@ implementation
          end;
       end;
 
-
     { reads the parameter for a subroutine call }
-    procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+    procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;para:tnode;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
       var
          membercall,
          prevafterassn : boolean;
          i        : integer;
-         para,p2  : tnode;
+         p2  : tnode;
          currpara : tparavarsym;
          aprocdef : tprocdef;
       begin
@@ -1060,36 +1059,39 @@ implementation
            end
          else
            begin
-             para:=nil;
-             if anon_inherited then
-              begin
-                if not assigned(current_procinfo) then
-                  internalerror(200305054);
-                for i:=0 to current_procinfo.procdef.paras.count-1 do
+             { parse params if needed }
+             if not assigned(para) then
+               begin
+                 if anon_inherited then
                   begin
-                    currpara:=tparavarsym(current_procinfo.procdef.paras[i]);
-                    if not(vo_is_hidden_para in currpara.varoptions) then
+                    if not assigned(current_procinfo) then
+                      internalerror(200305054);
+                    for i:=0 to current_procinfo.procdef.paras.count-1 do
                       begin
-                        { inheritance by msgint? }
-                        if assigned(srdef) then
-                          { anonymous inherited via msgid calls only require a var parameter for
-                            both methods, so we need some type casting here }
-                          para:=ccallparanode.create(ctypeconvnode.create_internal(ctypeconvnode.create_internal(
-                            cloadnode.create(currpara,currpara.owner),cformaltype),tparavarsym(tprocdef(srdef).paras[i]).vardef),
-                          para)
-                        else
-                          para:=ccallparanode.create(cloadnode.create(currpara,currpara.owner),para);
-                      end;
-                 end;
-              end
-             else
-              begin
-                if try_to_consume(_LKLAMMER) then
-                 begin
-                   para:=parse_paras(false,false,_RKLAMMER);
-                   consume(_RKLAMMER);
-                 end;
-              end;
+                        currpara:=tparavarsym(current_procinfo.procdef.paras[i]);
+                        if not(vo_is_hidden_para in currpara.varoptions) then
+                          begin
+                            { inheritance by msgint? }
+                            if assigned(srdef) then
+                              { anonymous inherited via msgid calls only require a var parameter for
+                                both methods, so we need some type casting here }
+                              para:=ccallparanode.create(ctypeconvnode.create_internal(ctypeconvnode.create_internal(
+                                cloadnode.create(currpara,currpara.owner),cformaltype),tparavarsym(tprocdef(srdef).paras[i]).vardef),
+                              para)
+                            else
+                              para:=ccallparanode.create(cloadnode.create(currpara,currpara.owner),para);
+                          end;
+                     end;
+                  end
+                 else
+                  begin
+                    if try_to_consume(_LKLAMMER) then
+                     begin
+                       para:=parse_paras(false,false,_RKLAMMER);
+                       consume(_RKLAMMER);
+                     end;
+                  end;
+               end;
              { indicate if this call was generated by a member and
                no explicit self is used, this is needed to determine
                how to handle a destructor call (PFV) }
@@ -1274,9 +1276,9 @@ implementation
          paras.free;
       end;
 
-
     { the ID token has to be consumed before calling this function }
-    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
+
+    procedure do_member_read(structh:tabstractrecorddef;getaddr:boolean;sym:tsym;paras:tnode;var p1:tnode;var again:boolean;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
       var
         isclassref:boolean;
         isrecordtype:boolean;
@@ -1315,7 +1317,7 @@ implementation
               case sym.typ of
                  procsym:
                    begin
-                      do_proc_call(sym,sym.owner,structh,
+                      do_proc_call(sym,sym.owner,structh,paras,
                                    (getaddr and not(token in [_CARET,_POINT])),
                                    again,p1,callflags,spezcontext);
                       { we need to know which procedure is called }
@@ -1419,7 +1421,6 @@ implementation
            end;
       end;
 
-
     function handle_specialize_inline_specialization(var srsym:tsym;out srsymtable:tsymtable;out spezcontext:tspecializationcontext):boolean;
       var
         spezdef : tdef;
@@ -1489,6 +1490,40 @@ implementation
             end;
       end;
 
+    { returns true if the symbol is a generic dummy which should
+      attempt to parse parameters and infer specialization }
+    function should_infer_specialization(sym:tsym):boolean;inline;
+      begin
+        result:=(sp_generic_dummy in sym.symoptions) and (sym.typ=typesym) and (m_implicit_function_specialization in current_settings.modeswitches);
+      end;
+
+    { parses parameters and passes results to try_implicit_specialization.
+      this function is used to handle cases where there is only a typesym
+      generic dummy which could infer specialization. }
+    function handle_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_member:boolean;out para: tnode;out spezcontext:tspecializationcontext):tdef;
+      begin
+        { parse paramaters }
+        if try_to_consume(_LKLAMMER) then
+          begin
+            para:=parse_paras(false,false,_RKLAMMER);
+            { inferred specialization requires params }
+            if not assigned(para) then
+              begin
+                result:=generrordef;
+                exit;
+              end;
+            consume(_RKLAMMER);
+          end
+        else
+          { inferred specialization requires params }
+          begin
+            result:=generrordef;
+            exit;
+          end;
+        if assigned(para) then
+          tcallparanode(para).get_paratype;
+        result:=try_implicit_specialization(sym,struct,is_member,para,spezcontext);
+      end;
 
     function handle_factor_typenode(hdef:tdef;getaddr:boolean;var again:boolean;sym:tsym;typeonly:boolean):tnode;
       var
@@ -1498,6 +1533,8 @@ implementation
         isspecialize : boolean;
         spezcontext : tspecializationcontext;
         savedfilepos : tfileposinfo;
+        pdef:tdef;
+        para:tnode;
       begin
          spezcontext:=nil;
          if sym=nil then
@@ -1560,7 +1597,7 @@ implementation
                      consume(_ID);
                    end;
                  if result.nodetype<>errorn then
-                   do_member_read(tabstractrecorddef(hdef),false,srsym,result,again,[],spezcontext)
+                   do_member_read(tabstractrecorddef(hdef),false,srsym,nil,result,again,[],spezcontext)
                  else
                    spezcontext.free;
                end
@@ -1584,6 +1621,7 @@ implementation
                 else
                   isspecialize:=false;
                 erroroutresult:=true;
+                para:=nil;
                 { TP allows also @TMenu.Load if Load is only }
                 { defined in an anchestor class              }
                 srsym:=search_struct_member(tabstractrecorddef(hdef),pattern);
@@ -1599,6 +1637,15 @@ implementation
                       begin
                         savedfilepos:=current_filepos;
                         consume(_ID);
+                        if should_infer_specialization(srsym) then
+                          begin
+                            pdef:=handle_implicit_specialization(srsym,tabstractrecorddef(hdef),true,para,spezcontext);
+                            if pdef<>generrordef then
+                              begin
+                                srsym:=tprocdef(pdef).procsym;
+                                srsymtable:=srsym.owner;
+                              end;
+                          end;
                         if not (sp_generic_dummy in srsym.symoptions) or
                             not (token in [_LT,_LSHARPBRACKET]) then
                           check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,savedfilepos)
@@ -1616,7 +1663,7 @@ implementation
                   end
                 else
                   if result.nodetype<>specializen then
-                    do_member_read(tabstractrecorddef(hdef),getaddr,srsym,result,again,[],spezcontext);
+                    do_member_read(tabstractrecorddef(hdef),getaddr,srsym,para,result,again,[],spezcontext);
               end;
            end
          else
@@ -1658,7 +1705,7 @@ implementation
                         (srsym.typ=procsym) and
                         (token in [_CARET,_POINT]) then
                        result:=cloadvmtaddrnode.create(result);
-                     do_member_read(tabstractrecorddef(hdef),getaddr,srsym,result,again,[],nil);
+                     do_member_read(tabstractrecorddef(hdef),getaddr,srsym,nil,result,again,[],nil);
                    end
                   else
                    begin
@@ -1954,7 +2001,7 @@ implementation
                     end;
                   check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg);
                   consume(_ID);
-                  do_member_read(nil,getaddr,srsym,node,again,[],nil);
+                  do_member_read(nil,getaddr,srsym,nil,node,again,[],nil);
                 end;
             end;
         end;
@@ -1981,6 +2028,8 @@ implementation
      strdef : tdef;
      spezcontext : tspecializationcontext;
      old_current_filepos : tfileposinfo;
+     para : tnode;
+     hdef : tdef;
     label
      skipreckklammercheck,
      skippointdefcheck;
@@ -2333,6 +2382,7 @@ implementation
                        begin
                          erroroutp1:=true;
                          srsym:=nil;
+                         para:=nil;
                          structh:=tabstractrecorddef(p1.resultdef);
                          if isspecialize then
                            begin
@@ -2355,6 +2405,15 @@ implementation
                                begin
                                  old_current_filepos:=current_filepos;
                                  consume(_ID);
+                                 if should_infer_specialization(srsym) then
+                                   begin
+                                     hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                     if hdef<>generrordef then
+                                       begin
+                                         srsym:=tprocdef(hdef).procsym;
+                                         srsymtable:=srsym.owner;
+                                       end;
+                                   end;
                                  if not (sp_generic_dummy in srsym.symoptions) or
                                      not (token in [_LT,_LSHARPBRACKET]) then
                                    check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2376,7 +2435,7 @@ implementation
                            end
                          else
                            if p1.nodetype<>specializen then
-                             do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                             do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                        end
                      else
                      consume(_ID);
@@ -2489,6 +2548,7 @@ implementation
                       if token=_ID then
                         begin
                           srsym:=nil;
+                          para:=nil;
                           structh:=tobjectdef(tclassrefdef(p1.resultdef).pointeddef);
                           if isspecialize then
                             begin
@@ -2511,6 +2571,15 @@ implementation
                                 begin
                                   old_current_filepos:=current_filepos;
                                   consume(_ID);
+                                  if should_infer_specialization(srsym) then
+                                    begin
+                                      hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                      if hdef<>generrordef then
+                                        begin
+                                          srsym:=tprocdef(hdef).procsym;
+                                          srsymtable:=srsym.owner;
+                                        end;
+                                    end;
                                   if not (sp_generic_dummy in srsym.symoptions) or
                                       not (token in [_LT,_LSHARPBRACKET]) then
                                     check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2532,7 +2601,7 @@ implementation
                             end
                           else
                             if p1.nodetype<>specializen then
-                              do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                              do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                         end
                       else { Error }
                         Consume(_ID);
@@ -2543,6 +2612,7 @@ implementation
                         begin
                           erroroutp1:=true;
                           srsym:=nil;
+                          para:=nil;
                           structh:=tobjectdef(p1.resultdef);
                           if isspecialize then
                             begin
@@ -2565,6 +2635,15 @@ implementation
                                 begin
                                    old_current_filepos:=current_filepos;
                                    consume(_ID);
+                                   if should_infer_specialization(srsym) then
+                                     begin
+                                       hdef:=handle_implicit_specialization(srsym,structh,false,para,spezcontext);
+                                       if hdef<>generrordef then
+                                         begin
+                                           srsym:=tprocdef(hdef).procsym;
+                                           srsymtable:=srsym.owner;
+                                         end;
+                                     end;
                                    if not (sp_generic_dummy in srsym.symoptions) or
                                        not (token in [_LT,_LSHARPBRACKET]) then
                                      check_hints(srsym,srsym.symoptions,srsym.deprecatedmsg,old_current_filepos)
@@ -2586,7 +2665,7 @@ implementation
                             end
                           else
                             if p1.nodetype<>specializen then
-                              do_member_read(structh,getaddr,srsym,p1,again,[],spezcontext);
+                              do_member_read(structh,getaddr,srsym,para,p1,again,[],spezcontext);
                         end
                       else { Error }
                         Consume(_ID);
@@ -2601,7 +2680,7 @@ implementation
                           if search_objc_method(pattern,srsym,srsymtable) then
                             begin
                               consume(_ID);
-                              do_proc_call(srsym,srsymtable,nil,
+                              do_proc_call(srsym,srsymtable,nil,nil,
                                 (getaddr and not(token in [_CARET,_POINT])),
                                 again,p1,[cnf_objc_id_call],nil);
                               { we need to know which procedure is called }
@@ -2771,6 +2850,9 @@ implementation
            dummypos,
            tokenpos: tfileposinfo;
            spezcontext : tspecializationcontext;
+           genname : string;
+           origsym : tsym;
+           para: tnode;
          begin
            { allow post fix operators }
            again:=true;
@@ -2779,6 +2861,7 @@ implementation
            tokenpos:=current_filepos;
            p1:=nil;
            spezcontext:=nil;
+           para:=nil;
 
            { avoid warning }
            fillchar(dummypos,sizeof(dummypos),0);
@@ -2919,14 +3002,44 @@ implementation
                      )
                    ) then
                  begin
-                   srsym:=resolve_generic_dummysym(srsym.name);
-                   if assigned(srsym) then
-                     srsymtable:=srsym.owner
-                   else
-                     begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
-                     end;
+                  if should_infer_specialization(srsym) then
+                    begin
+                      hdef:=handle_implicit_specialization(srsym,nil,false,para,spezcontext);
+                      if hdef<>generrordef then
+                        begin
+                          srsym:=tprocdef(hdef).procsym;
+                          srsymtable:=srsym.owner;
+                        end
+                      else
+                        begin
+                          srsym:=resolve_generic_dummysym(srsym.name);
+                          if assigned(srsym) then
+                            srsymtable:=srsym.owner
+                          else
+                            begin
+                              srsymtable:=nil;
+                              wasgenericdummy:=true;
+                            end;
+                        end;
+                    end
+                  else
+                    begin
+                      origsym:=srsym;
+                      srsym:=resolve_generic_dummysym(srsym.name);
+                      if assigned(srsym) then
+                        srsymtable:=srsym.owner
+                      else
+                        begin
+                          { the dummy could not be resolved so call node
+                            will attempt to infer specialization during
+                            type checking }
+                          if m_implicit_function_specialization in current_settings.modeswitches then
+                            srsym:=origsym
+                          else
+                            srsymtable:=nil;
+                          wasgenericdummy:=true;
+                        end;
+                    end;
                  end;
 
                { check hints, but only if it isn't a potential generic symbol;
@@ -3074,7 +3187,7 @@ implementation
                         {  e.g., "with classinstance do field := 5"), then    }
                         { let do_member_read handle it                        }
                         if (srsym.owner.symtabletype in [ObjectSymtable,recordsymtable]) then
-                          do_member_read(tabstractrecorddef(hdef),getaddr,srsym,p1,again,[],nil)
+                          do_member_read(tabstractrecorddef(hdef),getaddr,srsym,nil,p1,again,[],nil)
                         else
                           { otherwise it's a regular record subscript }
                           p1:=csubscriptnode.create(srsym,p1);
@@ -3168,7 +3281,7 @@ implementation
                         { withsymtable as well                          }
                         if (srsym.owner.symtabletype in [ObjectSymtable,recordsymtable]) then
                           begin
-                            do_member_read(tabstractrecorddef(hdef),getaddr,srsym,p1,again,[],spezcontext);
+                            do_member_read(tabstractrecorddef(hdef),getaddr,srsym,nil,p1,again,[],spezcontext);
                             spezcontext:=nil;
                           end
                         else
@@ -3188,9 +3301,10 @@ implementation
                           tmpgetaddr:=getaddr and not(token in [_POINT,_LECKKLAMMER])
                         else
                           tmpgetaddr:=getaddr and not(token in [_CARET,_POINT,_LECKKLAMMER]);
-                        do_proc_call(srsym,srsymtable,nil,tmpgetaddr,
+                        do_proc_call(srsym,srsymtable,nil,para,tmpgetaddr,
                                      again,p1,callflags,spezcontext);
                         spezcontext:=nil;
+                        para:=nil;
                       end;
                   end;
 
@@ -3216,7 +3330,7 @@ implementation
                         { not srsymtable.symtabletype since that can be }
                         { withsymtable as well                          }
                         if (srsym.owner.symtabletype in [ObjectSymtable,recordsymtable]) then
-                          do_member_read(tabstractrecorddef(hdef),getaddr,srsym,p1,again,[],nil)
+                          do_member_read(tabstractrecorddef(hdef),getaddr,srsym,nil,p1,again,[],nil)
                         else
                           { no propertysyms in records (yet) }
                           internalerror(2009111510);
@@ -3578,7 +3692,7 @@ implementation
                        include(current_procinfo.flags,pi_has_inherited);
                        if anon_inherited then
                          include(callflags,cnf_anon_inherited);
-                       do_member_read(hclassdef,getaddr,srsym,p1,again,callflags,nil);
+                       do_member_read(hclassdef,getaddr,srsym,nil,p1,again,callflags,nil);
                      end
                     else
                      begin
@@ -3593,7 +3707,7 @@ implementation
                                  (srsym.typ<>procsym) then
                                 internalerror(200303171);
                               p1:=nil;
-                              do_proc_call(srsym,srsym.owner,hclassdef,false,again,p1,[],nil);
+                              do_proc_call(srsym,srsym.owner,hclassdef,nil,false,again,p1,[],nil);
                             end
                           else
                             begin
@@ -4118,7 +4232,7 @@ implementation
                 else
                   internalerror(2015092703);
               end;
-              do_member_read(structdef,getaddr,gensym,result,again,[],spezcontext);
+              do_member_read(structdef,getaddr,gensym,nil,result,again,[],spezcontext);
             end
           else
             begin
@@ -4137,7 +4251,7 @@ implementation
                       { withsymtable as well                          }
                       if (gensym.owner.symtabletype in [ObjectSymtable,recordsymtable]) then
                         begin
-                          do_member_read(tabstractrecorddef(parseddef),getaddr,gensym,result,again,[],spezcontext);
+                          do_member_read(tabstractrecorddef(parseddef),getaddr,gensym,nil,result,again,[],spezcontext);
                           spezcontext:=nil;
                         end
                       else
@@ -4147,7 +4261,7 @@ implementation
                   else
                     begin
                       { regular procedure/function call }
-                      do_proc_call(gensym,gensym.owner,nil,
+                      do_proc_call(gensym,gensym.owner,nil,nil,
                                    (getaddr and not(token in [_CARET,_POINT,_LECKKLAMMER])),
                                    again,result,[],spezcontext);
                       spezcontext:=nil;
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 7760a4e134..3e2660cc5f 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,9 +33,12 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
-
+  
+    function try_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_struct_member:boolean;para: tnode;out spezcontext:tspecializationcontext):tdef;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string;parsedtype:tdef;symname:string;parsedpos:tfileposinfo);inline;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string);inline;
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;inline;
@@ -65,16 +68,94 @@ uses
   { common }
   cutils,fpccrc,
   { global }
-  globals,tokens,verbose,finput,
+  sysutils,globals,tokens,verbose,finput,
   { symtable }
   symconst,symsym,symtable,defcmp,procinfo,
   { modules }
   fmodule,
-  node,nobj,
+  nobj,ncal,
+  htypechk,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub;
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;var prettyname,specializename:ansistring);
+      var
+        namepart : string;
+        prettynamepart : ansistring;
+        module : tmodule;
+      begin
+        module:=find_module_from_symtable(paramtype.owner);
+        if not assigned(module) then
+          internalerror(2016112802);
+        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+paramtype.unique_id_str;
+        { we use the full name of the type to uniquely identify it }
+        if (symtablestack.top.symtabletype=parasymtable) and
+            (symtablestack.top.defowner.typ=procdef) and
+            (paramtype.owner=symtablestack.top) then
+          begin
+            { special handling for specializations inside generic function declarations }
+            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
+          end
+        else
+          begin
+            prettynamepart:=paramtype.fullownerhierarchyname(true);
+          end;
+        specializename:=specializename+namepart;
+        if not first then
+          prettyname:=prettyname+',';
+        prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
+    procedure make_genname(def_list_count:integer;symname:string;genericdef:tdef;out genname,ugenname:string);
+      var
+        countstr: string;
+        gencount,
+        i: integer;
+      begin
+        { use the name of the symbol as procvars return a user friendly version
+          of the name }
+        if symname='' then
+          genname:=ttypesym(genericdef.typesym).realname
+        else
+          genname:=symname;
+
+        { in case of non-Delphi mode the type name could already be a generic
+          def (but maybe the wrong one) }
+        if assigned(genericdef) and
+            ([df_generic,df_specialization]*genericdef.defoptions<>[]) then
+          begin
+            { remove the type count suffix from the generic's name }
+            for i:=Length(genname) downto 1 do
+              if genname[i]='$' then
+                begin
+                  genname:=copy(genname,1,i-1);
+                  break;
+                end;
+            { in case of a specialization we've only reached the specialization
+              checksum yet }
+            if df_specialization in genericdef.defoptions then
+              for i:=length(genname) downto 1 do
+                if genname[i]='$' then
+                  begin
+                    genname:=copy(genname,1,i-1);
+                    break;
+                  end;
+          end
+        else
+          begin
+            split_generic_name(genname,ugenname,gencount);
+            if genname<>ugenname then
+              genname:=ugenname;
+          end;
+
+        { search a generic with the given count of params }
+        countstr:='';
+        str(def_list_count,countstr);
+
+        genname:=genname+'$'+countstr;
+        ugenname:=upper(genname);
+      end;
 
     procedure maybe_add_waiting_unit(tt:tdef);
       var
@@ -308,7 +389,6 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
       begin
         result:=true;
@@ -372,26 +452,7 @@ uses
                     else if (typeparam.resultdef.typ<>errordef) then
                       begin
                         genericdeflist.Add(typeparam.resultdef);
-                        module:=find_module_from_symtable(typeparam.resultdef.owner);
-                        if not assigned(module) then
-                          internalerror(2016112802);
-                        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+typeparam.resultdef.unique_id_str;
-                        { we use the full name of the type to uniquely identify it }
-                        if (symtablestack.top.symtabletype=parasymtable) and
-                            (symtablestack.top.defowner.typ=procdef) and
-                            (typeparam.resultdef.owner=symtablestack.top) then
-                          begin
-                            { special handling for specializations inside generic function declarations }
-                            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
-                          end
-                        else
-                          begin
-                            prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true);
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,prettyname,specializename);
                       end;
                   end
                 else
@@ -428,6 +489,183 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function generate_implicit_specialization(out context:tspecializationcontext;symname:string;struct:tabstractrecorddef;is_struct_member:boolean;paramtypes:tfplist;paracount:integer):tdef;
+      var
+        parsedpos:tfileposinfo;
+        poslist:tfplist;
+        found: boolean;
+        i: longint;
+        ugenname: string;
+        paramtype: tdef;
+        parampos : pfileposinfo;
+        tmpparampos : tfileposinfo;
+      begin
+        result:=nil;        
+        context:=tspecializationcontext.create;
+        fillchar(parsedpos,sizeof(parsedpos),0);
+        poslist:=context.poslist;
+        tmpparampos:=current_filepos;
+
+        { parse_generic_specialization_types_internal }
+        for i := 0 to min(paramtypes.count,paracount)-1 do
+          begin
+            paramtype:=tdef(paramtypes[i]);
+            if assigned(poslist) then
+              begin
+                new(parampos);
+                parampos^:=tmpparampos;
+                poslist.add(parampos);
+              end;
+            context.genericdeflist.Add(paramtype);
+            make_prettystring(paramtype,true,context.prettyname,context.specializename);
+          end;
+
+        { generate_specialization_phase1 }
+        make_genname(context.genericdeflist.Count,symname,nil,context.genname,ugenname);
+
+        if assigned(struct) then
+          found:=searchsym_in_struct(struct,is_struct_member,ugenname,context.sym,context.symtable,[ssf_search_helper])
+        else
+          found:=searchsym(ugenname,context.sym,context.symtable);
+
+        if not found or not (context.sym.typ in [typesym,procsym]) then
+          begin
+            context.free;
+            context:=nil;
+            result:=generrordef;
+            exit;
+          end;
+
+        { we've found the correct def }
+        if context.sym.typ=typesym then
+          result:=tstoreddef(ttypesym(context.sym).typedef)
+        else
+          begin
+            if tprocsym(context.sym).procdeflist.count=0 then
+              internalerror(2015061203);
+            result:=tstoreddef(tprocsym(context.sym).procdefList[0]);
+          end;
+      end;
+
+    function try_implicit_specialization(sym:tsym;struct:tabstractrecorddef;is_struct_member:boolean;para: tnode;out spezcontext:tspecializationcontext):tdef;
+      { insert def to front of list (params are processed in reverse order)
+        and only if the param is unique. }
+      procedure add_unique_param(list:tfplist;def:tdef);inline;
+        begin
+          if list.indexof(def)=-1 then
+            list.insert(0,def);
+        end;
+      var
+        i, p: integer;
+        srsym: tsym;
+        srsymtable: TSymtable;
+        ignorevisibility,
+        allowdefaultparas,
+        objcidcall,
+        explicitunit,
+        searchhelpers,
+        anoninherited: boolean;
+        count: integer;
+        bestpd: tabstractprocdef;
+        genname: string;
+        candidates: tcallcandidates;
+        pt: tcallparanode;
+        paraindex: integer;
+        paramtypes: tfplist;
+        paradef: tdef;
+        typename: string;
+        newtype: ttypesym;
+      begin
+        result:=nil;
+        paramtypes:=nil;
+        candidates:=nil;
+        { count params }
+        paraindex:=0;
+        pt:=tcallparanode(para);
+        while assigned(pt) do
+          begin
+            pt:=tcallparanode(pt.nextpara);
+            paraindex:=paraindex+1;
+          end;
+        { find generic procsyms by param count, starting from
+          number of parsed params. if a procsym is found then
+          validate via tcallcandidates and build list of *unique*
+          types for use when specializing.
+        
+          inferred generic types are evaluated by inserting
+          non-repating types into the list in linear order.
+
+            (1,'string') = <Integer,String>
+            (1,2,3,4,5,6) = <Integer>
+            ('a','b') = <String>
+            ('string',1) = <String,Integer>
+            ('a',1,'b',2,'c') = <String,Integer>
+        }
+        for i:=paraindex downto 1 do
+          begin
+            genname:=sym.name+'$'+tostr(i);
+            if assigned(struct) then
+              searchsym_in_struct(struct,is_struct_member,genname,srsym,srsymtable,[ssf_search_helper])
+            else
+              searchsym(genname,srsym,srsymtable);
+            if assigned(srsym) then
+              begin
+                ignorevisibility:=false;
+                allowdefaultparas:=true;
+                objcidcall:=false;
+                explicitunit:=false;
+                searchhelpers:=false;
+                anoninherited:=false;
+                spezcontext:=nil;
+                candidates:=tcallcandidates.create(tprocsym(srsym),srsym.owner,para,ignorevisibility,
+                  allowdefaultparas,objcidcall,explicitunit,
+                  searchhelpers,anoninherited,spezcontext);
+                if candidates.count>0 then
+                  begin
+                    candidates.get_information;
+                    count:=candidates.choose_best(bestpd,false);
+                    if count>0 then
+                      begin
+                        if not assigned(paramtypes) then
+                          paramtypes:=tfplist.create;
+                        pt:=tcallparanode(para);
+                        paraindex:=0;
+                        while assigned(pt) do
+                          begin
+                            paradef:=pt.paravalue.resultdef;
+                            { there is no typesym for the def so create a temporary one }
+                            if paradef.typesym=nil then
+                              begin
+                                typename:='$'+srsym.realname+'$'+hexstr(paradef);
+                                newtype:=ctypesym.create(typename,paradef,false);
+                                newtype.owner:=paradef.owner;
+                                { TODO: newtype is never released
+                                  we could release it in the spezcontext but
+                                  I don't see where this is released either }
+                              end;
+                            if paradef.typesym=nil then
+                              internalerror(2019022602);
+                            add_unique_param(paramtypes,paradef);
+                            pt:=tcallparanode(pt.nextpara);
+                            paraindex:=paraindex+1;
+                          end;
+                        if paramtypes.count>0 then
+                          begin
+                            result:=generate_implicit_specialization(spezcontext,sym.realname,struct,is_struct_member,paramtypes,i);
+                            paramtypes.clear;
+                            if result<>generrordef then
+                              break;
+                          end;
+                      end;
+                  end;
+                freeandnil(candidates);
+              end;
+          end;
+        freeandnil(paramtypes);
+        freeandnil(candidates);
+        if result=nil then
+          result:=generrordef;
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
@@ -589,49 +827,7 @@ uses
             exit;
           end;
 
-        { use the name of the symbol as procvars return a user friendly version
-          of the name }
-        if symname='' then
-          genname:=ttypesym(genericdef.typesym).realname
-        else
-          genname:=symname;
-
-        { in case of non-Delphi mode the type name could already be a generic
-          def (but maybe the wrong one) }
-        if assigned(genericdef) and
-            ([df_generic,df_specialization]*genericdef.defoptions<>[]) then
-          begin
-            { remove the type count suffix from the generic's name }
-            for i:=Length(genname) downto 1 do
-              if genname[i]='$' then
-                begin
-                  genname:=copy(genname,1,i-1);
-                  break;
-                end;
-            { in case of a specialization we've only reached the specialization
-              checksum yet }
-            if df_specialization in genericdef.defoptions then
-              for i:=length(genname) downto 1 do
-                if genname[i]='$' then
-                  begin
-                    genname:=copy(genname,1,i-1);
-                    break;
-                  end;
-          end
-        else
-          begin
-            split_generic_name(genname,ugenname,gencount);
-            if genname<>ugenname then
-              genname:=ugenname;
-          end;
-
-        { search a generic with the given count of params }
-        countstr:='';
-        str(context.genericdeflist.Count,countstr);
-
-        genname:=genname+'$'+countstr;
-        ugenname:=upper(genname);
-
+        make_genname(context.genericdeflist.Count,symname,genericdef,genname,ugenname);
         context.genname:=genname;
 
         if assigned(genericdef) and (genericdef.owner.symtabletype in [objectsymtable,recordsymtable]) then
diff --git a/compiler/pinline.pas b/compiler/pinline.pas
index 91b5055dc0..99d3b79051 100644
--- a/compiler/pinline.pas
+++ b/compiler/pinline.pas
@@ -122,7 +122,7 @@ implementation
                  exit;
               end;
 
-            do_member_read(classh,false,sym,p2,again,[],nil);
+            do_member_read(classh,false,sym,nil,p2,again,[],nil);
 
             { we need the real called method }
             do_typecheckpass(p2);
@@ -235,11 +235,11 @@ implementation
                 else
                   callflag:=cnf_dispose_call;
                 if is_new then
-                  do_member_read(classh,false,sym,p2,again,[callflag],nil)
+                  do_member_read(classh,false,sym,nil,p2,again,[callflag],nil)
                 else
                   begin
                     if not(m_fpc in current_settings.modeswitches) then
-                      do_member_read(classh,false,sym,p2,again,[callflag],nil)
+                      do_member_read(classh,false,sym,nil,p2,again,[callflag],nil)
                     else
                       begin
                         p2:=ccallnode.create(nil,tprocsym(sym),sym.owner,p2,[callflag],nil);
@@ -472,7 +472,7 @@ implementation
             afterassignment:=false;
             searchsym_in_class(classh,classh,pattern,srsym,srsymtable,[ssf_search_helper]);
             consume(_ID);
-            do_member_read(classh,false,srsym,p1,again,[cnf_new_call],nil);
+            do_member_read(classh,false,srsym,nil,p1,again,[cnf_new_call],nil);
             { we need to know which procedure is called }
             do_typecheckpass(p1);
             if not(
diff --git a/compiler/ppu.pas b/compiler/ppu.pas
index 10c42e7eb8..31011be3e8 100644
--- a/compiler/ppu.pas
+++ b/compiler/ppu.pas
@@ -43,7 +43,7 @@ type
 {$endif Test_Double_checksum}
 
 const
-  CurrentPPUVersion = 201;
+  CurrentPPUVersion = 203;
 
 { unit flags }
   uf_init                = $000001; { unit has initialization section }
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 796b2d6736..893ae64d27 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -340,6 +340,7 @@ interface
     function  searchsym_in_named_module(const unitname, symname: TIDString; out srsym: tsym; out srsymtable: tsymtable): boolean;
     function  searchsym_in_class(classh: tobjectdef; contextclassh:tabstractrecorddef;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;
     function  searchsym_in_record(recordh:tabstractrecorddef;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable):boolean;
+    function  searchsym_in_struct(structh:tabstractrecorddef;ismember:boolean;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;inline;
     function  searchsym_in_class_by_msgint(classh:tobjectdef;msgid:longint;out srdef : tdef;out srsym:tsym;out srsymtable:TSymtable):boolean;
     function  searchsym_in_class_by_msgstr(classh:tobjectdef;const s:string;out srsym:tsym;out srsymtable:TSymtable):boolean;
     { searches symbols inside of a helper's implementation }
@@ -3621,6 +3622,26 @@ implementation
         srsymtable:=nil;
       end;
 
+    function  searchsym_in_struct(structh:tabstractrecorddef;ismember:boolean;const s : TIDString;out srsym:tsym;out srsymtable:TSymtable;flags:tsymbol_search_flags):boolean;
+      begin
+        if structh.typ=recorddef then
+          begin
+            if ismember then
+              begin
+                srsym:=search_struct_member(structh,s);
+                if assigned(srsym) then
+                  srsymtable:=srsym.owner;
+                result := assigned(srsym);
+              end
+            else
+              result:=searchsym_in_record(structh,s,srsym,srsymtable);
+          end
+        else if structh.typ=objectdef then
+          result:=searchsym_in_class(tobjectdef(structh),tobjectdef(structh),s,srsym,srsymtable,flags)
+        else
+          internalerror(2019030203);
+      end;
+
     function searchsym_in_class_by_msgint(classh:tobjectdef;msgid:longint;out srdef : tdef;out srsym:tsym;out srsymtable:TSymtable):boolean;
       var
         def : tdef;
diff --git a/tests/test/timpfuncspez1.pp b/tests/test/timpfuncspez1.pp
new file mode 100644
index 0000000000..3bef43318d
--- /dev/null
+++ b/tests/test/timpfuncspez1.pp
@@ -0,0 +1,27 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez1;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis<T>',sizeof(msg));
+end;
+
+var
+	a0: array of byte;
+	a1: set of char;
+	a2: record i: integer end;
+const
+	c0 = 'string';
+	c1 = [1,2,3];
+	c2 = nil;
+begin
+	DoThis(a0);
+	DoThis(a1);
+	DoThis(a2);
+
+	DoThis(c0);
+	DoThis(c1);
+	DoThis(c2);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez10.pp b/tests/test/timpfuncspez10.pp
new file mode 100644
index 0000000000..4be7026087
--- /dev/null
+++ b/tests/test/timpfuncspez10.pp
@@ -0,0 +1,76 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez10;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+var
+	s1: string;
+	s2: ansistring;
+	s3: widestring;
+	s4: unicodestring;
+	s5: pchar;
+	s6: ansichar;
+	s7: char;
+	s8: widechar;
+	s9: unicodechar;
+
+	i1: byte;
+	i2: shortint;
+	i3: smallint;
+	i4: word;
+	i5: integer;
+	i6: cardinal;
+	i7: longint;
+	i8: longword;
+	i9: int64;
+	i10: qword;
+
+	r1: Real;
+	r2: single;
+	r3: double;
+	r4: extended;
+	r5: comp;
+	r6: currency;
+
+	b1: boolean;
+	b2: bytebool;
+	b3: wordbool;
+	b4: longbool;
+begin
+	DoThis(s1);
+	DoThis(s2);
+	DoThis(s3);
+	DoThis(s4);
+	DoThis(s5);
+	DoThis(s6);
+	DoThis(s7);
+	DoThis(s8);
+
+	DoThis(i1);
+	DoThis(i2);
+	DoThis(i3);
+	DoThis(i4);
+	DoThis(i5);
+	DoThis(i6);
+	DoThis(i7);
+	DoThis(i8);
+	DoThis(i9);
+	DoThis(i10);
+
+	DoThis(r1);
+	DoThis(r2);
+	DoThis(r3);
+	DoThis(r4);
+	DoThis(r5);
+	DoThis(r6);
+
+	DoThis(b1);
+	DoThis(b2);
+	DoThis(b3);
+	DoThis(b4);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez11.pp b/tests/test/timpfuncspez11.pp
new file mode 100644
index 0000000000..3b63355ab6
--- /dev/null
+++ b/tests/test/timpfuncspez11.pp
@@ -0,0 +1,15 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez11;
+
+generic procedure ClearMem<T: record>(var r: T);
+begin
+	FillChar(r, sizeof(T), 0);
+end;
+
+var
+	r: record i: integer end;
+begin
+	ClearMem(r);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez12.pp b/tests/test/timpfuncspez12.pp
new file mode 100644
index 0000000000..ff05a7e643
--- /dev/null
+++ b/tests/test/timpfuncspez12.pp
@@ -0,0 +1,19 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez12;
+
+procedure DoThis(msg: integer); overload;
+begin
+	writeln('DoThis:',msg);
+end;
+
+generic procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1:',msg);
+end;
+
+begin
+	DoThis(1);
+	DoThis('aaa');
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez13.pp b/tests/test/timpfuncspez13.pp
new file mode 100644
index 0000000000..3238a5bba0
--- /dev/null
+++ b/tests/test/timpfuncspez13.pp
@@ -0,0 +1,21 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez13;
+
+generic procedure DoThis<T,U>(param1: T; param2: U);
+begin
+	writeln('DoThis<T,U>',sizeof(param1),' ',sizeof(param2));
+end;
+
+generic procedure DoThis<T>(msg: T);
+var
+	i: T;
+begin
+	writeln('DoThis<T>',sizeof(msg), ' ', i);
+	DoThis(i,[1,2,3]);
+end;
+
+begin
+	DoThis('string');
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez2.pp b/tests/test/timpfuncspez2.pp
new file mode 100644
index 0000000000..6468d73dc8
--- /dev/null
+++ b/tests/test/timpfuncspez2.pp
@@ -0,0 +1,37 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez2;
+
+generic procedure DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+begin
+	DoThis(1);											// DoThis$1#1:1
+	DoThis(1, 1);										// DoThis$1#2:1 1
+	DoThis('a', 'a');								// DoThis$1#2:a a
+	DoThis('a', 1);									// DoThis$2#2:a 1
+	DoThis('a', 1, TObject.Create);	// DoThis$2#3:a 1 TObject
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez3.pp b/tests/test/timpfuncspez3.pp
new file mode 100644
index 0000000000..2b1f49cc21
--- /dev/null
+++ b/tests/test/timpfuncspez3.pp
@@ -0,0 +1,49 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez3;
+
+type
+	TMyObject = class
+		generic procedure DoThis<T>(msg: T);
+		generic procedure DoThis<T>(msg: T; param1:T);
+		generic procedure DoThis<T,U>(msg: T);
+		generic procedure DoThis<T,U>(msg: T; param1: U);
+		generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+	end;
+
+generic procedure TMyObject.DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure TMyObject.DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure TMyObject.DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+var
+	obj: TMyObject;
+begin
+	obj := TMyObject.Create;
+	obj.DoThis(1);
+	obj.DoThis('hello');
+	obj.DoThis(1, 1);
+	obj.DoThis('hello', 'hello');
+	obj.DoThis('hello', 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez4.pp b/tests/test/timpfuncspez4.pp
new file mode 100644
index 0000000000..4989a1268f
--- /dev/null
+++ b/tests/test/timpfuncspez4.pp
@@ -0,0 +1,25 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez4;
+
+type
+	TMyObject = class
+		generic class procedure DoThis<T>(param1: T);
+		generic class procedure DoThis<T,U>(param1: T; param2: U);
+	end;
+
+generic class procedure TMyObject.DoThis<T>(param1: T);
+begin
+	writeln('DoThis$1#1:', param1);
+end;
+
+generic class procedure TMyObject.DoThis<T,U>(param1: T; param2: U);
+begin
+	writeln('DoThis$2#2:', param1, ',', param2);
+end;
+
+begin
+	TMyObject.DoThis(10);
+	TMyObject.DoThis(10, 'hello');
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez5.pp b/tests/test/timpfuncspez5.pp
new file mode 100644
index 0000000000..b411c0c5ad
--- /dev/null
+++ b/tests/test/timpfuncspez5.pp
@@ -0,0 +1,26 @@
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez5;
+
+var
+	Res: integer = 0;
+
+procedure DoThis(msg: integer); overload;
+begin
+	writeln('DoThis:',msg);
+	Res := 1;
+end;
+
+generic procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1:',msg);
+	Res := 2;
+end;
+
+begin
+	DoThis(1);
+	// non-generic function takes precedence so Res must be 1
+	if Res <> 1 then
+		Halt(1);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez6.pp b/tests/test/timpfuncspez6.pp
new file mode 100644
index 0000000000..593ed6fa35
--- /dev/null
+++ b/tests/test/timpfuncspez6.pp
@@ -0,0 +1,16 @@
+{%FAIL}
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez6;
+
+generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+end;
+
+begin
+	DoThis('aa', 'aa', TObject.Create);
+	// wil be specialized as DoThis(msg: integer; param1: TObject; param2: TObject)
+	// so we expect an incompatible type error
+	DoThis(1, 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez7.pp b/tests/test/timpfuncspez7.pp
new file mode 100644
index 0000000000..c86792aea8
--- /dev/null
+++ b/tests/test/timpfuncspez7.pp
@@ -0,0 +1,49 @@
+{$mode objfpc}
+{$modeswitch advancedrecords}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez7;
+
+type
+	TMyRecord = record
+		generic procedure DoThis<T>(msg: T);
+		generic procedure DoThis<T>(msg: T; param1:T);
+		generic procedure DoThis<T,U>(msg: T);
+		generic procedure DoThis<T,U>(msg: T; param1: U);
+		generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);
+	end;
+
+generic procedure TMyRecord.DoThis<T>(msg: T);
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+generic procedure TMyRecord.DoThis<T>(msg: T; param1: T);
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T);
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T; param1: U);
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+generic procedure TMyRecord.DoThis<T,U>(msg: T; param1: U; param2: TObject);
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+var
+	obj: TMyRecord;
+begin
+	obj.DoThis(1);
+	obj.DoThis('hello');
+	obj.DoThis(1, 1);
+	obj.DoThis('hello', 'hello');
+	obj.DoThis('hello', 1, TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez8.pp b/tests/test/timpfuncspez8.pp
new file mode 100644
index 0000000000..7f3cf1beb8
--- /dev/null
+++ b/tests/test/timpfuncspez8.pp
@@ -0,0 +1,17 @@
+{%FAIL}
+{$mode objfpc}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez8;
+
+type
+	TMyObject = class
+	end;
+
+generic procedure DoThis<T: TMyObject>(obj: T);
+begin
+end;
+
+begin
+	DoThis(TObject.Create);
+end.
\ No newline at end of file
diff --git a/tests/test/timpfuncspez9.pp b/tests/test/timpfuncspez9.pp
new file mode 100644
index 0000000000..64c7d25715
--- /dev/null
+++ b/tests/test/timpfuncspez9.pp
@@ -0,0 +1,37 @@
+{$mode delphi}
+{$modeswitch implicitfunctionspecialization}
+
+program timpfuncspez9;
+
+procedure DoThis<T>(msg: T); overload;
+begin
+	writeln('DoThis$1#1:',msg);
+end;
+
+procedure DoThis<T>(msg: T; param1: T); overload;
+begin
+	writeln('DoThis$1#2:',msg,' ',param1);
+end;
+
+procedure DoThis<T,U>(msg: T); overload;
+begin
+	writeln('DoThis$2#1:',msg);
+end;
+
+procedure DoThis<T,U>(msg: T; param1: U); overload;
+begin
+	writeln('DoThis$2#2:',msg,' ',param1);
+end;
+
+procedure DoThis<T,U>(msg: T; param1: U; param2: TObject); overload;
+begin
+	writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
+end;
+
+begin
+	DoThis(1);											// DoThis$1#1:1
+	DoThis(1, 1);										// DoThis$1#2:1 1
+	DoThis('a', 'a');								// DoThis$1#2:a a
+	DoThis('a', 1);									// DoThis$2#2:a 1
+	DoThis('a', 1, TObject.Create);	// DoThis$2#3:a 1 TObject
+end.
\ No newline at end of file
-- 
2.17.2 (Apple Git-113)

patch_3_25.diff (62,704 bytes)   

Ryan Joseph

2019-03-25 15:02

reporter   ~0115041

Uploaded a new patch which fixed the bug (all tests run now) and renamed the mode switch to "implicitfunctionspecialization" and changed tests names to timpfuncspez*.pp.

Ryan Joseph

2019-11-23 16:27

reporter   ~0119458

Just wanted to make a note here so I don't forget that this patch is on hold until I change the way I handled overloads and some other things (see a mail list thread titled "generic proc inference" from October 3rd 2019). In worst case scenario 90% of the work needs to be redone. I'm also not going to work on this until we get the generic constants patch settled and out of the way because it's been in limbo for over 6 months now.

Ryan Joseph

2020-03-30 11:03

reporter   ~0121762

I've done an overhaul on the previous design so I'm posting a patch which is a draft version (with debug writeln's as placeholders for proper error messages). If the design is sane I will clean everything up and probably inline some functions.

1) the entire inference process is redone according to feedback (see try_implicit_specialization).
   - array types and function pointers can be used for specialization.
   - templates can appear in any order now DoThis<T,U>(a: U; b: T);
2) as was requested by Sven the dummy syms are now valid procsyms and are passed along to tcallcandiates for overloading.
3) The inference is done at one location in do_proc_call but we could possibly move that inside tcallnode.pass_typecheck if it were better for some reason.
4) I added a tprocsym.add_generic_candiate to associate procsyms with dummy procsyms but I wasn't sure if this was good design so it's not cpu compliment and thus specialization from units will fail.
5) non-generic overloads are filtered in htypechk.is_better_candidate but I don't feel like I integrated my check in the right place due to me not really understanding that function.
patch_3_30_20.diff (28,487 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index 68a994bea4..7fe655d985 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_implicit_function_specialization    { attempt to specialize generic function by inferring types from parameters }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -642,7 +643,7 @@ interface
 
        cstylearrayofconst = [pocall_cdecl,pocall_cppdecl,pocall_mwpascal,pocall_sysv_abi_cdecl,pocall_ms_abi_cdecl];
 
-       modeswitchstr : array[tmodeswitch] of string[18] = ('',
+       modeswitchstr : array[tmodeswitch] of string[30] = ('',
          '','','','','','','',
          {$ifdef gpc_mode}'',{$endif}
          { more specific }
@@ -683,7 +684,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index e3b66a6f75..4fec5d3e5a 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2772,9 +2772,16 @@ implementation
         if assigned(spezcontext) then
           begin
             if not (df_generic in pd.defoptions) then
-              internalerror(2015060301);
+              begin
+                { it's possible for non-generic procs to be included
+                  if m_implicit_function_specialization is enabled }
+                if m_implicit_function_specialization in current_settings.modeswitches then
+                  exit(true)
+                else
+                  internalerror(2015060301);
+              end;
             { check whether the given parameters are compatible
-              to the def's constraints }
+              to the def's constraints } 
             if not check_generic_constraints(pd,spezcontext.genericdeflist,spezcontext.poslist) then
               exit;
             def:=generate_specialization_phase2(spezcontext,pd,false,'');
@@ -3264,7 +3271,7 @@ implementation
          if currpd^.invalid then
           res:=-1
         else
-         begin
+         begin           
            { less operator parameters? }
            res:=(bestpd^.coper_count-currpd^.coper_count);
            if (res=0) then
@@ -3319,6 +3326,17 @@ implementation
               end;
             end;
          end;
+        { if a specialization is better than a non-specialization then
+          the non-generic always wins }
+        if m_implicit_function_specialization in current_settings.modeswitches then
+          begin
+            writeln('is_better_candidate:',res,' currpd:',currpd^.data.is_specialization,' bestpd:', bestpd^.data.is_specialization);
+            if (res<=-1) and not currpd^.invalid and (not currpd^.data.is_specialization and bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                res:=1;
+              end;
+          end;
         is_better_candidate:=res;
       end;
 
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index e91df24718..2661d84e10 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3540,7 +3540,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
       var
         candidates : tcallcandidates;
@@ -3556,6 +3555,7 @@ implementation
         statements : tstatementnode;
         converted_result_data : ttempcreatenode;
         calltype: tdispcalltype;
+        pdef: tdef;
       begin
          result:=nil;
          candidates:=nil;
diff --git a/compiler/pdecsub.pas b/compiler/pdecsub.pas
index b3f6d4e5f7..43f02a9e77 100644
--- a/compiler/pdecsub.pas
+++ b/compiler/pdecsub.pas
@@ -1149,13 +1149,22 @@ implementation
               end;
             if not assigned(dummysym) then
               begin
-                dummysym:=ctypesym.create(orgspnongen,cundefineddef.create(true));
+                if m_implicit_function_specialization in current_settings.modeswitches then
+                  dummysym:=cprocsym.create(orgspnongen)
+                else
+                  dummysym:=ctypesym.create(orgspnongen,cundefineddef.create(true));
+                writeln('* create dummy ',dummysym.realname);
                 if assigned(astruct) then
                   astruct.symtable.insert(dummysym)
                 else
                   symtablestack.top.insert(dummysym);
               end;
             include(dummysym.symoptions,sp_generic_dummy);
+            writeln('* added dummy ',dummysym.realname,' for ',aprocsym.name);
+            { add the generic proc to the list of potential candiates
+              for implicit function specialization }
+            if dummysym.typ=procsym then
+              tprocsym(dummysym).add_generic_candiate(aprocsym);
             { start token recorder for the declaration }
             pd.init_genericdecl;
             current_scanner.startrecordtokens(pd.genericdecltokenbuf);
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index 250c96c668..a517fd2c99 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -976,7 +976,6 @@ implementation
          end;
       end;
 
-
     { reads the parameter for a subroutine call }
     procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
       var
@@ -1109,6 +1108,25 @@ implementation
                how to handle a destructor call (PFV) }
              if membercall then
                include(callflags,cnf_member_call);
+             { try to implictly specialize the dummy symbol }
+             if (sp_generic_dummy in sym.symoptions) and not try_implicit_specialization(sym,para,spezcontext) then
+               begin
+                 writeln('implicit specialization failed');
+                 { there are no other potential procdefs to attempt
+                   so it's safe to assume the call will fail and we
+                   can give a hard error }
+                 if tprocsym(sym).procdeflist.count=0 then
+                   begin
+                     writeln('**** Error: can''t be implicitly specialized.');
+                     Message(type_e_cant_choose_overload_function);
+                     p1:=cerrornode.create;
+                     exit;
+                   end;
+                 { if the dummy still has procdefs associated with it
+                   then these are non-generic and we can switch the procsym }
+                 sym:=tprocdef(tprocsym(sym).procdeflist[0]).procsym;
+                 writeln('switched to new procsym');
+               end;
              if assigned(obj) then
                begin
                  if not (st.symtabletype in [ObjectSymtable,recordsymtable]) then
@@ -2852,6 +2870,7 @@ implementation
            tokenpos: tfileposinfo;
            spezcontext : tspecializationcontext;
            cufflags : tconsume_unitsym_flags;
+           origsym: tsym;
          begin
            { allow post fix operators }
            again:=true;
@@ -3032,13 +3051,23 @@ implementation
                      )
                    ) then
                  begin
+                   origsym:=srsym;
                    srsym:=resolve_generic_dummysym(srsym.name);
                    if assigned(srsym) then
                      srsymtable:=srsym.owner
                    else
                      begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
+                       if (sp_generic_dummy in origsym.symoptions) and 
+                         (m_implicit_function_specialization in current_settings.modeswitches) then
+                         begin
+                           srsym:=origsym;
+                           srsymtable:=srsym.owner
+                         end
+                       else
+                         begin
+                           srsymtable:=nil;
+                           wasgenericdummy:=true;
+                         end;
                      end;
                  end;
 
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index e8489726aa..e01b99cd93 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,9 +33,12 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
 
+    function try_implicit_specialization(sym:tsym;para: tnode;out spezcontext:tspecializationcontext):boolean;  
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string;parsedtype:tdef;symname:string;parsedpos:tfileposinfo);inline;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string);inline;
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;inline;
@@ -65,16 +68,43 @@ uses
   { common }
   cutils,fpccrc,
   { global }
-  globals,tokens,verbose,finput,
+  sysutils,globals,tokens,verbose,finput,
   { symtable }
   symconst,symsym,symtable,defcmp,procinfo,
   { modules }
   fmodule,
-  node,nobj,
+  nobj,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;var prettyname,specializename:ansistring);
+      var
+        namepart : string;
+        prettynamepart : ansistring;
+        module : tmodule;
+      begin
+        module:=find_module_from_symtable(paramtype.owner);
+        if not assigned(module) then
+          internalerror(2016112802);
+        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+paramtype.unique_id_str;
+        { we use the full name of the type to uniquely identify it }
+        if (symtablestack.top.symtabletype=parasymtable) and
+            (symtablestack.top.defowner.typ=procdef) and
+            (paramtype.owner=symtablestack.top) then
+          begin
+            { special handling for specializations inside generic function declarations }
+            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
+          end
+        else
+          begin
+            prettynamepart:=paramtype.fullownerhierarchyname(true);
+          end;
+        specializename:=specializename+namepart;
+        if not first then
+          prettyname:=prettyname+',';
+        prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
 
     procedure maybe_add_waiting_unit(tt:tdef);
       var
@@ -308,7 +338,6 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
       begin
         result:=true;
@@ -372,26 +401,7 @@ uses
                     else if (typeparam.resultdef.typ<>errordef) then
                       begin
                         genericdeflist.Add(typeparam.resultdef);
-                        module:=find_module_from_symtable(typeparam.resultdef.owner);
-                        if not assigned(module) then
-                          internalerror(2016112802);
-                        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+typeparam.resultdef.unique_id_str;
-                        { we use the full name of the type to uniquely identify it }
-                        if (symtablestack.top.symtabletype=parasymtable) and
-                            (symtablestack.top.defowner.typ=procdef) and
-                            (typeparam.resultdef.owner=symtablestack.top) then
-                          begin
-                            { special handling for specializations inside generic function declarations }
-                            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
-                          end
-                        else
-                          begin
-                            prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true);
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,prettyname,specializename);
                       end;
                   end
                 else
@@ -428,6 +438,309 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function try_implicit_specialization(sym:tsym;para: tnode;out spezcontext:tspecializationcontext):boolean;
+
+      { makes the specialization context from the generic proc def and templates }
+      procedure generate_implicit_specialization(out context:tspecializationcontext;genericdef:tprocdef;templates:tfphashlist);
+        var
+          parsedpos:tfileposinfo;
+          poslist:tfplist;
+          i: longint;
+          paramtype: tdef;
+          parampos : pfileposinfo;
+          tmpparampos : tfileposinfo;
+          template: string;
+        begin
+          context:=tspecializationcontext.create;
+          fillchar(parsedpos,sizeof(parsedpos),0);
+          poslist:=context.poslist;
+          tmpparampos:=current_filepos;
+          for i:=0 to templates.count-1 do
+            begin
+              template:=ttypesym(genericdef.genericparas[i]).name;
+              paramtype:=ttypesym(templates.find(template)).typedef;
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.genericdeflist.Add(paramtype);
+              make_prettystring(paramtype,true,context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(sym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paraindex: integer;
+          paramtypes: tfplist;
+          typename: string;
+          newtype: ttypesym;
+          paradef:tdef;
+          i:integer;
+        begin
+          paramtypes:=tfplist.create;
+          pt:=tcallparanode(para);
+          paraindex:=0;
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { there is no typesym for the def so create a temporary one }
+              if paradef.typesym=nil then
+                begin
+                  typename:='$'+sym.realname+'$'+hexstr(paradef);
+                  newtype:=ctypesym.create(typename,paradef);
+                  newtype.owner:=paradef.owner;
+                  { TODO: newtype is never released
+                    we could release it in the spezcontext but
+                    I don't see where this is released either }
+                end;
+              if paradef.typesym=nil then
+                internalerror(2019022602);
+              paramtypes.insert(0,paradef);
+              pt:=tcallparanode(pt.nextpara);
+              paraindex:=paraindex+1;
+            end;
+          writeln('caller params:');
+          for i:=0 to paramtypes.count-1 do
+            begin
+              paradef:=tdef(paramtypes[i]);
+              writeln('  ',i,'=',paradef.typesym.realname, ' (',paradef.typ,')');
+            end;
+          result:=paramtypes;
+        end;
+
+      { compares generic template <T> with paravar for compatibility }
+      function is_generic_template_compatible(template:ttypesym; paravar:tparavarsym): boolean;
+        var
+          pd: tprocvardef;
+          i: integer;
+        begin
+          { handle array types by use element types }
+          if paravar.vardef.typ=arraydef then
+            result:=template.name=tarraydef(paravar.vardef).elementdef.typesym.name
+          else if paravar.vardef.typ=procvardef then
+            begin
+              pd:=tprocvardef(paravar.vardef);
+              { for procvar check if the template is used in one of the generic params }
+              result:=false;
+              for i:=0 to pd.genericparas.count-1 do
+                if ttypesym(pd.genericparas[i]).name=template.name then
+                  begin
+                    result:=true;
+                    break;
+                  end;
+            end
+          else
+            result:=template.name=paravar.vardef.typesym.name;
+        end;
+      
+      { infer type from procvar parameters }
+      function handle_procvars(var vardef: tdef; var paradef: tdef): boolean;
+        var
+          j, k: integer;
+          pd: tprocvardef;
+          paravar: tparavarsym;
+        begin
+          pd:=tprocvardef(vardef);
+          writeln('param is procvar');
+          { 1) generic TProc<S> = procedure(context: S); 
+            2) generic procedure Run<T,U>(proc: specialize TProc<T>; context: U);
+            3) procedure Do(obj: TMyClass);
+            4) Run(@Do);
+
+            first we find "T" in specialize TProc<T> so now we need to find 
+            the param number of the  generic template index in the eneric proc def.
+            
+            "T" is template #0 in specialize TProc<T>
+            so we match with "S" in TProc<S> (which is template #0).
+
+            this finally corresponds to parameter #0 of TProc<S> (which is "context") 
+            so we infer the type from that paramter number (of the caller procvar),
+            which is "TMyClass". }
+          for j:=0 to pd.genericparas.count-1 do
+            begin
+              writeln('  ',j,':',ttypesym(pd.genericparas[j]).name,':',ttypesym(tprocvardef(pd.genericdef).genericparas[j]).name);
+              for k:=0 to tprocvardef(pd.genericdef).paras.count-1 do
+                begin
+                  paravar:=tparavarsym(tprocvardef(pd.genericdef).paras[k]);
+                  writeln('  ',k,':',paravar.name,'=',paravar.vardef.typesym.name);
+                  if paravar.vardef.typesym.name=ttypesym(tprocvardef(pd.genericdef).genericparas[j]).name then
+                    begin
+                      paradef:=tparavarsym(tprocvardef(paradef).paras[k]).vardef;
+                      vardef:=ttypesym(pd.genericparas[j]).typedef;
+                      writeln('found ',vardef.typesym.name, ' is ', paradef.typesym.name, ' has constraints ',assigned(tstoreddef(vardef).genconstraintdata));
+                      exit(true);
+                    end;
+                end;
+            end;
+          result:=false;
+        end;
+
+      { compare generic template parameters <T> with call node parameters. }
+      function is_possible_specialization(caller_params:tfplist;genericdef:tprocdef;out templates:tfphashlist): boolean;
+        var
+          i, j, count: integer;
+          paravar: tparavarsym;
+          vardef,
+          paradef: tdef;
+          template: string;
+          index: integer;
+          paras: tfplist;
+        label
+          finished;
+        begin
+          result:=false;
+          { build list of visible generic parameters }
+          paras:=tfplist.create;
+          for i:=0 to genericdef.paras.count-1 do
+            begin
+              paravar:=tparavarsym(genericdef.paras[i]);
+              if (vo_is_hidden_para in paravar.varoptions) then
+                continue;
+              paras.add(paravar);
+            end;
+          { caller parameter and generic parameter counts don't match }
+          if caller_params.count<>paras.count then
+            goto finished;
+          
+          { check to make sure the generic templates
+            are all used at least once in the parameters }
+          count:=0;
+          for i:=0 to genericdef.genericparas.count-1 do
+            for j:=0 to paras.count-1 do
+              if is_generic_template_compatible(ttypesym(genericdef.genericparas[i]),tparavarsym(paras[j])) then
+                begin
+                  inc(count);
+                  break;
+                end;
+          if count<genericdef.genericparas.count then
+            goto finished;
+
+          templates:=tfphashlist.create;
+          for i:=0 to caller_params.count-1 do
+            begin
+              paradef:=tdef(caller_params[i]);
+              vardef:=tparavarsym(paras[i]).vardef;
+              { handle array types by use element types }
+              if vardef.typ=arraydef then
+                vardef:=tarraydef(vardef).elementdef;
+              if paradef.typ=arraydef then
+                paradef:=tarraydef(paradef).elementdef;
+              template:=vardef.typesym.name;
+              writeln('  ',i,'=',template,':',paradef.typesym.realname,' (',vardef.typ,':',paradef.typ,')');
+
+              { handle procvars }
+              if (vardef.typ=procvardef) and handle_procvars(vardef, paradef) then
+                template:=vardef.typesym.name;
+
+              { the param type is not a generic template (or a contrained type)
+                and the type needs to be checked }
+              if vardef.typ<>undefineddef then
+                begin
+                  if compare_defs(paradef,vardef,nothingn)=te_incompatible then
+                    goto finished;
+                  { if the vardef has generic constraints then it is still 
+                    a generic template and needs to be added }
+                  if tstoreddef(vardef).genconstraintdata=nil then
+                    continue;
+                end;
+              { the template is already used }
+              index:=templates.findindexof(template);
+              if index<>-1 then
+                begin
+                  if not equal_defs(ttypesym(templates[index]).typedef,paradef) then
+                    goto finished
+                  else
+                    continue;
+                end;
+              templates.add(vardef.typesym.name,paradef.typesym);
+            end;
+          result:=templates.count=genericdef.genericparas.count;
+          finished:
+          if not result then
+            begin
+              freeandnil(templates);
+              freeandnil(paras);
+            end;
+        end;
+
+      var
+        i,
+        def_index: integer;
+        srsym: tprocsym;
+        paramtypes: tfplist;
+        pd,
+        candidate: tprocdef;
+        possible: boolean;
+        dummypos: tfileposinfo;
+        candidates: tfplist;
+        dummysym: tprocsym;
+        templates: tfphashlist;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        candidate:=nil;
+        templates:=nil;
+        dummysym:=tprocsym(sym);
+        candidates:=dummysym.generic_candiates;
+
+        { dummy has no generic procs associated with it }
+        if candidates=nil then
+          exit(false);
+
+        { clear candidates from a previous call }
+        for i := 0 to dummysym.ProcdefList.count-1 do
+          begin
+            pd:=tprocdef(dummysym.ProcdefList[i]);
+            if pd.is_generic then
+              begin
+                dummysym.ProcdefList.delete(i);
+                break;
+              end;
+          end;
+
+        paramtypes:=make_param_list(sym,para);
+        for i:=0 to candidates.count-1 do
+          begin
+            srsym:=tprocsym(candidates[i]);
+            for def_index:=0 to srsym.ProcdefList.Count-1 do
+              begin
+                pd:=tprocdef(srsym.ProcdefList[def_index]);
+                writeln('try candidate: ',pd.fullprocname(true));
+                possible:=is_possible_specialization(paramtypes,pd,templates);
+                if possible then
+                  begin
+                    { we found multiple candiates so this is an ambiguous inference }
+                    if assigned(candidate) then
+                      begin
+                        writeln('**** Error: ambiguous inference between ',pd.typename,' and ', candidate.typename);
+                        Message(type_e_cant_choose_overload_function);
+                        goto finished;
+                      end;
+                    candidate:=pd;
+                  end;
+              end;
+          end;
+        if assigned(candidate) then
+          begin
+            writeln('found candidate ',candidate.typename,' for specialization');
+            generate_implicit_specialization(spezcontext,candidate,templates);
+            dummysym.ProcdefList.add(candidate);
+            result:=true;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+          freeandnil(templates);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 30f6a10f14..e8f1aff79f 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -131,6 +131,8 @@ interface
        protected
           FProcdefList   : TFPObjectList;
           FProcdefDerefList : TFPList;
+          { list of tprocsym which are associated with a dummysym }
+          FGenericCandidates : TFPList;
        public
           constructor create(const n : string);virtual;
           constructor ppuload(ppufile:tcompilerppufile);
@@ -154,6 +156,9 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           property ProcdefList:TFPObjectList read FProcdefList;
+          { dummy sym generic candiates for implicit function specialization }
+          property generic_candiates:tfplist read FGenericCandidates;
+          procedure add_generic_candiate(sym: tprocsym);
        end;
        tprocsymclass = class of tprocsym;
 
@@ -990,6 +995,13 @@ implementation
           end;
       end;
 
+    procedure tprocsym.add_generic_candiate(sym: tprocsym);
+      begin
+        if FGenericCandidates=nil then
+          FGenericCandidates:=tfpList.create;
+        if generic_candiates.indexof(sym)=-1 then
+          generic_candiates.add(sym);
+      end;  
 
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 7db43af8d2..df4a0c45d6 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3340,8 +3340,20 @@ implementation
       begin
         if sym.typ=procsym then
           begin
-            { A procsym is visible, when there is at least one of the procdefs visible }
             result:=false;
+            { check dummy sym visbility by following associated procsyms }
+            if (m_implicit_function_specialization in current_settings.modeswitches) and 
+              (sp_generic_dummy in sym.symoptions) and
+              assigned(tprocsym(sym).generic_candiates) then
+              begin
+                for i:=0 to tprocsym(sym).generic_candiates.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).generic_candiates[i]),contextobjdef) then
+                    begin
+                      result:=true;
+                      exit;
+                    end;
+              end;
+            { A procsym is visible, when there is at least one of the procdefs visible }
             for i:=0 to tprocsym(sym).ProcdefList.Count-1 do
               begin
                 pd:=tprocdef(tprocsym(sym).ProcdefList[i]);
patch_3_30_20.diff (28,487 bytes)   

Ryan Joseph

2020-03-30 11:24

reporter   ~0121763

Sorry, typo in the last patch, use this one instead.
patch_3_30_20.2.diff (28,488 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index 68a994bea4..7fe655d985 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -491,7 +491,8 @@ interface
          m_array_operators,     { use Delphi compatible array operators instead of custom ones ("+") }
          m_multi_helpers,       { helpers can appear in multiple scopes simultaneously }
          m_array2dynarray,      { regular arrays can be implicitly converted to dynamic arrays }
-         m_prefixed_attributes  { enable attributes that are defined before the type they belong to }
+         m_prefixed_attributes, { enable attributes that are defined before the type they belong to }
+         m_implicit_function_specialization    { attempt to specialize generic function by inferring types from parameters }
        );
        tmodeswitches = set of tmodeswitch;
 
@@ -642,7 +643,7 @@ interface
 
        cstylearrayofconst = [pocall_cdecl,pocall_cppdecl,pocall_mwpascal,pocall_sysv_abi_cdecl,pocall_ms_abi_cdecl];
 
-       modeswitchstr : array[tmodeswitch] of string[18] = ('',
+       modeswitchstr : array[tmodeswitch] of string[30] = ('',
          '','','','','','','',
          {$ifdef gpc_mode}'',{$endif}
          { more specific }
@@ -683,7 +684,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index e3b66a6f75..4fec5d3e5a 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2772,9 +2772,16 @@ implementation
         if assigned(spezcontext) then
           begin
             if not (df_generic in pd.defoptions) then
-              internalerror(2015060301);
+              begin
+                { it's possible for non-generic procs to be included
+                  if m_implicit_function_specialization is enabled }
+                if m_implicit_function_specialization in current_settings.modeswitches then
+                  exit(true)
+                else
+                  internalerror(2015060301);
+              end;
             { check whether the given parameters are compatible
-              to the def's constraints }
+              to the def's constraints } 
             if not check_generic_constraints(pd,spezcontext.genericdeflist,spezcontext.poslist) then
               exit;
             def:=generate_specialization_phase2(spezcontext,pd,false,'');
@@ -3264,7 +3271,7 @@ implementation
          if currpd^.invalid then
           res:=-1
         else
-         begin
+         begin           
            { less operator parameters? }
            res:=(bestpd^.coper_count-currpd^.coper_count);
            if (res=0) then
@@ -3319,6 +3326,17 @@ implementation
               end;
             end;
          end;
+        { if a specialization is better than a non-specialization then
+          the non-generic always wins }
+        if m_implicit_function_specialization in current_settings.modeswitches then
+          begin
+            writeln('is_better_candidate:',res,' currpd:',currpd^.data.is_specialization,' bestpd:', bestpd^.data.is_specialization);
+            if (res<=-1) and not currpd^.invalid and (not currpd^.data.is_specialization and bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                res:=1;
+              end;
+          end;
         is_better_candidate:=res;
       end;
 
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index e91df24718..2661d84e10 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3540,7 +3540,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
       var
         candidates : tcallcandidates;
@@ -3556,6 +3555,7 @@ implementation
         statements : tstatementnode;
         converted_result_data : ttempcreatenode;
         calltype: tdispcalltype;
+        pdef: tdef;
       begin
          result:=nil;
          candidates:=nil;
diff --git a/compiler/pdecsub.pas b/compiler/pdecsub.pas
index b3f6d4e5f7..43f02a9e77 100644
--- a/compiler/pdecsub.pas
+++ b/compiler/pdecsub.pas
@@ -1149,13 +1149,22 @@ implementation
               end;
             if not assigned(dummysym) then
               begin
-                dummysym:=ctypesym.create(orgspnongen,cundefineddef.create(true));
+                if m_implicit_function_specialization in current_settings.modeswitches then
+                  dummysym:=cprocsym.create(orgspnongen)
+                else
+                  dummysym:=ctypesym.create(orgspnongen,cundefineddef.create(true));
+                writeln('* create dummy ',dummysym.realname);
                 if assigned(astruct) then
                   astruct.symtable.insert(dummysym)
                 else
                   symtablestack.top.insert(dummysym);
               end;
             include(dummysym.symoptions,sp_generic_dummy);
+            writeln('* added dummy ',dummysym.realname,' for ',aprocsym.name);
+            { add the generic proc to the list of potential candiates
+              for implicit function specialization }
+            if dummysym.typ=procsym then
+              tprocsym(dummysym).add_generic_candiate(aprocsym);
             { start token recorder for the declaration }
             pd.init_genericdecl;
             current_scanner.startrecordtokens(pd.genericdecltokenbuf);
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index 250c96c668..a517fd2c99 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -976,7 +976,6 @@ implementation
          end;
       end;
 
-
     { reads the parameter for a subroutine call }
     procedure do_proc_call(sym:tsym;st:TSymtable;obj:tabstractrecorddef;getaddr:boolean;var again : boolean;var p1:tnode;callflags:tcallnodeflags;spezcontext:tspecializationcontext);
       var
@@ -1109,6 +1108,25 @@ implementation
                how to handle a destructor call (PFV) }
              if membercall then
                include(callflags,cnf_member_call);
+             { try to implictly specialize the dummy symbol }
+             if (sp_generic_dummy in sym.symoptions) and not try_implicit_specialization(sym,para,spezcontext) then
+               begin
+                 writeln('implicit specialization failed');
+                 { there are no other potential procdefs to attempt
+                   so it's safe to assume the call will fail and we
+                   can give a hard error }
+                 if tprocsym(sym).procdeflist.count=0 then
+                   begin
+                     writeln('**** Error: can''t be implicitly specialized.');
+                     Message(type_e_cant_choose_overload_function);
+                     p1:=cerrornode.create;
+                     exit;
+                   end;
+                 { if the dummy still has procdefs associated with it
+                   then these are non-generic and we can switch the procsym }
+                 sym:=tprocdef(tprocsym(sym).procdeflist[0]).procsym;
+                 writeln('switched to new procsym');
+               end;
              if assigned(obj) then
                begin
                  if not (st.symtabletype in [ObjectSymtable,recordsymtable]) then
@@ -2852,6 +2870,7 @@ implementation
            tokenpos: tfileposinfo;
            spezcontext : tspecializationcontext;
            cufflags : tconsume_unitsym_flags;
+           origsym: tsym;
          begin
            { allow post fix operators }
            again:=true;
@@ -3032,13 +3051,23 @@ implementation
                      )
                    ) then
                  begin
+                   origsym:=srsym;
                    srsym:=resolve_generic_dummysym(srsym.name);
                    if assigned(srsym) then
                      srsymtable:=srsym.owner
                    else
                      begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
+                       if (sp_generic_dummy in origsym.symoptions) and 
+                         (m_implicit_function_specialization in current_settings.modeswitches) then
+                         begin
+                           srsym:=origsym;
+                           srsymtable:=srsym.owner
+                         end
+                       else
+                         begin
+                           srsymtable:=nil;
+                           wasgenericdummy:=true;
+                         end;
                      end;
                  end;
 
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index e8489726aa..92e72edca7 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,9 +33,12 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
 
+    function try_implicit_specialization(sym:tsym;para: tnode;out spezcontext:tspecializationcontext):boolean;  
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string;parsedtype:tdef;symname:string;parsedpos:tfileposinfo);inline;
     procedure generate_specialization(var tt:tdef;parse_class_parent:boolean;_prettyname:string);inline;
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;inline;
@@ -65,16 +68,43 @@ uses
   { common }
   cutils,fpccrc,
   { global }
-  globals,tokens,verbose,finput,
+  sysutils,globals,tokens,verbose,finput,
   { symtable }
   symconst,symsym,symtable,defcmp,procinfo,
   { modules }
   fmodule,
-  node,nobj,
+  nobj,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;var prettyname,specializename:ansistring);
+      var
+        namepart : string;
+        prettynamepart : ansistring;
+        module : tmodule;
+      begin
+        module:=find_module_from_symtable(paramtype.owner);
+        if not assigned(module) then
+          internalerror(2016112802);
+        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+paramtype.unique_id_str;
+        { we use the full name of the type to uniquely identify it }
+        if (symtablestack.top.symtabletype=parasymtable) and
+            (symtablestack.top.defowner.typ=procdef) and
+            (paramtype.owner=symtablestack.top) then
+          begin
+            { special handling for specializations inside generic function declarations }
+            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
+          end
+        else
+          begin
+            prettynamepart:=paramtype.fullownerhierarchyname(true);
+          end;
+        specializename:=specializename+namepart;
+        if not first then
+          prettyname:=prettyname+',';
+        prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
 
     procedure maybe_add_waiting_unit(tt:tdef);
       var
@@ -308,7 +338,6 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
       begin
         result:=true;
@@ -372,26 +401,7 @@ uses
                     else if (typeparam.resultdef.typ<>errordef) then
                       begin
                         genericdeflist.Add(typeparam.resultdef);
-                        module:=find_module_from_symtable(typeparam.resultdef.owner);
-                        if not assigned(module) then
-                          internalerror(2016112802);
-                        namepart:='_$'+hexstr(module.moduleid,8)+'$$'+typeparam.resultdef.unique_id_str;
-                        { we use the full name of the type to uniquely identify it }
-                        if (symtablestack.top.symtabletype=parasymtable) and
-                            (symtablestack.top.defowner.typ=procdef) and
-                            (typeparam.resultdef.owner=symtablestack.top) then
-                          begin
-                            { special handling for specializations inside generic function declarations }
-                            prettynamepart:=tdef(symtablestack.top.defowner).fullownerhierarchyname(true)+tprocdef(symtablestack.top.defowner).procsym.prettyname;
-                          end
-                        else
-                          begin
-                            prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true);
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,prettyname,specializename);
                       end;
                   end
                 else
@@ -428,6 +438,309 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function try_implicit_specialization(sym:tsym;para: tnode;out spezcontext:tspecializationcontext):boolean;
+
+      { makes the specialization context from the generic proc def and templates }
+      procedure generate_implicit_specialization(out context:tspecializationcontext;genericdef:tprocdef;templates:tfphashlist);
+        var
+          parsedpos:tfileposinfo;
+          poslist:tfplist;
+          i: longint;
+          paramtype: tdef;
+          parampos : pfileposinfo;
+          tmpparampos : tfileposinfo;
+          template: string;
+        begin
+          context:=tspecializationcontext.create;
+          fillchar(parsedpos,sizeof(parsedpos),0);
+          poslist:=context.poslist;
+          tmpparampos:=current_filepos;
+          for i:=0 to templates.count-1 do
+            begin
+              template:=ttypesym(genericdef.genericparas[i]).name;
+              paramtype:=ttypesym(templates.find(template)).typedef;
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.genericdeflist.Add(paramtype);
+              make_prettystring(paramtype,true,context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(sym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paraindex: integer;
+          paramtypes: tfplist;
+          typename: string;
+          newtype: ttypesym;
+          paradef:tdef;
+          i:integer;
+        begin
+          paramtypes:=tfplist.create;
+          pt:=tcallparanode(para);
+          paraindex:=0;
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { there is no typesym for the def so create a temporary one }
+              if paradef.typesym=nil then
+                begin
+                  typename:='$'+sym.realname+'$'+hexstr(paradef);
+                  newtype:=ctypesym.create(typename,paradef);
+                  newtype.owner:=paradef.owner;
+                  { TODO: newtype is never released
+                    we could release it in the spezcontext but
+                    I don't see where this is released either }
+                end;
+              if paradef.typesym=nil then
+                internalerror(2019022602);
+              paramtypes.insert(0,paradef);
+              pt:=tcallparanode(pt.nextpara);
+              paraindex:=paraindex+1;
+            end;
+          writeln('caller params:');
+          for i:=0 to paramtypes.count-1 do
+            begin
+              paradef:=tdef(paramtypes[i]);
+              writeln('  ',i,'=',paradef.typesym.realname, ' (',paradef.typ,')');
+            end;
+          result:=paramtypes;
+        end;
+
+      { compares generic template <T> with paravar for compatibility }
+      function is_generic_template_compatible(template:ttypesym; paravar:tparavarsym): boolean;
+        var
+          pd: tprocvardef;
+          i: integer;
+        begin
+          { handle array types by use element types }
+          if paravar.vardef.typ=arraydef then
+            result:=template.name=tarraydef(paravar.vardef).elementdef.typesym.name
+          else if paravar.vardef.typ=procvardef then
+            begin
+              pd:=tprocvardef(paravar.vardef);
+              { for procvar check if the template is used in one of the generic params }
+              result:=false;
+              for i:=0 to pd.genericparas.count-1 do
+                if ttypesym(pd.genericparas[i]).name=template.name then
+                  begin
+                    result:=true;
+                    break;
+                  end;
+            end
+          else
+            result:=template.name=paravar.vardef.typesym.name;
+        end;
+      
+      { infer type from procvar parameters }
+      function handle_procvars(var vardef: tdef; var paradef: tdef): boolean;
+        var
+          j, k: integer;
+          pd: tprocvardef;
+          paravar: tparavarsym;
+        begin
+          pd:=tprocvardef(vardef);
+          writeln('param is procvar');
+          { 1) generic TProc<S> = procedure(context: S); 
+            2) generic procedure Run<T,U>(proc: specialize TProc<T>; context: U);
+            3) procedure Do(obj: TMyClass);
+            4) Run(@Do);
+
+            first we find "T" in specialize TProc<T> so now we need to find 
+            the param number of the  generic template index in the eneric proc def.
+            
+            "T" is template #0 in specialize TProc<T>
+            so we match with "S" in TProc<S> (which is template #0).
+
+            this finally corresponds to parameter #0 of TProc<S> (which is "context") 
+            so we infer the type from that paramter number (of the caller procvar),
+            which is "TMyClass". }
+          for j:=0 to pd.genericparas.count-1 do
+            begin
+              writeln('  ',j,':',ttypesym(pd.genericparas[j]).name,':',ttypesym(tprocvardef(pd.genericdef).genericparas[j]).name);
+              for k:=0 to tprocvardef(pd.genericdef).paras.count-1 do
+                begin
+                  paravar:=tparavarsym(tprocvardef(pd.genericdef).paras[k]);
+                  writeln('  ',k,':',paravar.name,'=',paravar.vardef.typesym.name);
+                  if paravar.vardef.typesym.name=ttypesym(tprocvardef(pd.genericdef).genericparas[j]).name then
+                    begin
+                      paradef:=tparavarsym(tprocvardef(paradef).paras[k]).vardef;
+                      vardef:=ttypesym(pd.genericparas[j]).typedef;
+                      writeln('found ',vardef.typesym.name, ' is ', paradef.typesym.name, ' has constraints ',assigned(tstoreddef(vardef).genconstraintdata));
+                      exit(true);
+                    end;
+                end;
+            end;
+          result:=false;
+        end;
+
+      { compare generic template parameters <T> with call node parameters. }
+      function is_possible_specialization(caller_params:tfplist;genericdef:tprocdef;out templates:tfphashlist): boolean;
+        var
+          i, j, count: integer;
+          paravar: tparavarsym;
+          vardef,
+          paradef: tdef;
+          template: string;
+          index: integer;
+          paras: tfplist;
+        label
+          finished;
+        begin
+          result:=false;
+          { build list of visible generic parameters }
+          paras:=tfplist.create;
+          for i:=0 to genericdef.paras.count-1 do
+            begin
+              paravar:=tparavarsym(genericdef.paras[i]);
+              if (vo_is_hidden_para in paravar.varoptions) then
+                continue;
+              paras.add(paravar);
+            end;
+          { caller parameter and generic parameter counts don't match }
+          if caller_params.count<>paras.count then
+            goto finished;
+          
+          { check to make sure the generic templates
+            are all used at least once in the parameters }
+          count:=0;
+          for i:=0 to genericdef.genericparas.count-1 do
+            for j:=0 to paras.count-1 do
+              if is_generic_template_compatible(ttypesym(genericdef.genericparas[i]),tparavarsym(paras[j])) then
+                begin
+                  inc(count);
+                  break;
+                end;
+          if count<>genericdef.genericparas.count then
+            goto finished;
+
+          templates:=tfphashlist.create;
+          for i:=0 to caller_params.count-1 do
+            begin
+              paradef:=tdef(caller_params[i]);
+              vardef:=tparavarsym(paras[i]).vardef;
+              { handle array types by use element types }
+              if vardef.typ=arraydef then
+                vardef:=tarraydef(vardef).elementdef;
+              if paradef.typ=arraydef then
+                paradef:=tarraydef(paradef).elementdef;
+              template:=vardef.typesym.name;
+              writeln('  ',i,'=',template,':',paradef.typesym.realname,' (',vardef.typ,':',paradef.typ,')');
+
+              { handle procvars }
+              if (vardef.typ=procvardef) and handle_procvars(vardef, paradef) then
+                template:=vardef.typesym.name;
+
+              { the param type is not a generic template (or a contrained type)
+                and the type needs to be checked }
+              if vardef.typ<>undefineddef then
+                begin
+                  if compare_defs(paradef,vardef,nothingn)=te_incompatible then
+                    goto finished;
+                  { if the vardef has generic constraints then it is still 
+                    a generic template and needs to be added }
+                  if tstoreddef(vardef).genconstraintdata=nil then
+                    continue;
+                end;
+              { the template is already used }
+              index:=templates.findindexof(template);
+              if index<>-1 then
+                begin
+                  if not equal_defs(ttypesym(templates[index]).typedef,paradef) then
+                    goto finished
+                  else
+                    continue;
+                end;
+              templates.add(vardef.typesym.name,paradef.typesym);
+            end;
+          result:=templates.count=genericdef.genericparas.count;
+          finished:
+          if not result then
+            begin
+              freeandnil(templates);
+              freeandnil(paras);
+            end;
+        end;
+
+      var
+        i,
+        def_index: integer;
+        srsym: tprocsym;
+        paramtypes: tfplist;
+        pd,
+        candidate: tprocdef;
+        possible: boolean;
+        dummypos: tfileposinfo;
+        candidates: tfplist;
+        dummysym: tprocsym;
+        templates: tfphashlist;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        candidate:=nil;
+        templates:=nil;
+        dummysym:=tprocsym(sym);
+        candidates:=dummysym.generic_candiates;
+
+        { dummy has no generic procs associated with it }
+        if candidates=nil then
+          exit(false);
+
+        { clear candidates from a previous call }
+        for i := 0 to dummysym.ProcdefList.count-1 do
+          begin
+            pd:=tprocdef(dummysym.ProcdefList[i]);
+            if pd.is_generic then
+              begin
+                dummysym.ProcdefList.delete(i);
+                break;
+              end;
+          end;
+
+        paramtypes:=make_param_list(sym,para);
+        for i:=0 to candidates.count-1 do
+          begin
+            srsym:=tprocsym(candidates[i]);
+            for def_index:=0 to srsym.ProcdefList.Count-1 do
+              begin
+                pd:=tprocdef(srsym.ProcdefList[def_index]);
+                writeln('try candidate: ',pd.fullprocname(true));
+                possible:=is_possible_specialization(paramtypes,pd,templates);
+                if possible then
+                  begin
+                    { we found multiple candiates so this is an ambiguous inference }
+                    if assigned(candidate) then
+                      begin
+                        writeln('**** Error: ambiguous inference between ',pd.typename,' and ', candidate.typename);
+                        Message(type_e_cant_choose_overload_function);
+                        goto finished;
+                      end;
+                    candidate:=pd;
+                  end;
+              end;
+          end;
+        if assigned(candidate) then
+          begin
+            writeln('found candidate ',candidate.typename,' for specialization');
+            generate_implicit_specialization(spezcontext,candidate,templates);
+            dummysym.ProcdefList.add(candidate);
+            result:=true;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+          freeandnil(templates);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 30f6a10f14..e8f1aff79f 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -131,6 +131,8 @@ interface
        protected
           FProcdefList   : TFPObjectList;
           FProcdefDerefList : TFPList;
+          { list of tprocsym which are associated with a dummysym }
+          FGenericCandidates : TFPList;
        public
           constructor create(const n : string);virtual;
           constructor ppuload(ppufile:tcompilerppufile);
@@ -154,6 +156,9 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           property ProcdefList:TFPObjectList read FProcdefList;
+          { dummy sym generic candiates for implicit function specialization }
+          property generic_candiates:tfplist read FGenericCandidates;
+          procedure add_generic_candiate(sym: tprocsym);
        end;
        tprocsymclass = class of tprocsym;
 
@@ -990,6 +995,13 @@ implementation
           end;
       end;
 
+    procedure tprocsym.add_generic_candiate(sym: tprocsym);
+      begin
+        if FGenericCandidates=nil then
+          FGenericCandidates:=tfpList.create;
+        if generic_candiates.indexof(sym)=-1 then
+          generic_candiates.add(sym);
+      end;  
 
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 7db43af8d2..df4a0c45d6 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3340,8 +3340,20 @@ implementation
       begin
         if sym.typ=procsym then
           begin
-            { A procsym is visible, when there is at least one of the procdefs visible }
             result:=false;
+            { check dummy sym visbility by following associated procsyms }
+            if (m_implicit_function_specialization in current_settings.modeswitches) and 
+              (sp_generic_dummy in sym.symoptions) and
+              assigned(tprocsym(sym).generic_candiates) then
+              begin
+                for i:=0 to tprocsym(sym).generic_candiates.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).generic_candiates[i]),contextobjdef) then
+                    begin
+                      result:=true;
+                      exit;
+                    end;
+              end;
+            { A procsym is visible, when there is at least one of the procdefs visible }
             for i:=0 to tprocsym(sym).ProcdefList.Count-1 do
               begin
                 pd:=tprocdef(tprocsym(sym).ProcdefList[i]);
patch_3_30_20.2.diff (28,488 bytes)   

Issue History

Date Modified Username Field Change
2019-03-23 14:43 Ryan Joseph New Issue
2019-03-23 14:43 Ryan Joseph File Added: gen-implicit.diff
2019-03-24 11:04 Florian Note Added: 0115013
2019-03-24 14:56 Ryan Joseph Note Added: 0115020
2019-03-24 16:54 Ryan Joseph Note Added: 0115022
2019-03-25 15:00 Ryan Joseph File Added: patch_3_25.diff
2019-03-25 15:02 Ryan Joseph Note Added: 0115041
2019-10-04 00:06 Sven Barth Tag Attached: generics
2019-11-23 16:27 Ryan Joseph Note Added: 0119458
2020-03-30 11:03 Ryan Joseph File Added: patch_3_30_20.diff
2020-03-30 11:03 Ryan Joseph Note Added: 0121762
2020-03-30 11:24 Ryan Joseph File Added: patch_3_30_20.2.diff
2020-03-30 11:24 Ryan Joseph Note Added: 0121763