View Issue Details

IDProjectCategoryView StatusLast Update
0035261FPCCompilerpublic2021-03-06 19:18
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)   

Sven Barth

2020-10-24 19:52

manager   ~0126519

Last edited: 2020-10-24 19:53

View 3 revisions

I've now found the time to at least quickly look over your patch. I'm ignoring try_implicit_specialization for now, cause there is a more important problem: you did not adhere to what I wrote in my mail from a bit more than a year ago on 8th October (here: https://lists.freepascal.org/pipermail/fpc-pascal/2019-October/056951.html ):

I checked again. The thing is that in the compiler overload handling is done inside htypechk.tcallcandidates. Your patch weakens/changes that. So you'll have to completely rethink your solution to fit it into tcallcandidates (that should for example collect all suitable generics in addition to the non-generic ones (if any)).

In essence the heart of your changes should be inside tcallcandidates and nowhere else (aside from maybe some support functionality). For example there should be no need to create any dummy symbol or add defs to the procsym.

Ryan Joseph

2020-10-24 20:08

reporter   ~0126520

Thank you for finally reviewing this. I'm struggling to remember where I was at with so many months ago but I *think* what I was doing is adding proc defs to the dummy proc sym which are then resolved in tcallcandidates. Is that not a valid approach?

Ryan Joseph

2020-10-25 17:51

reporter   ~0126546

After reviewing this in more detail I'm still not 100% sure what you want me to do. tcallnode requires a spezcontext and we need to generate this by looking at the generic procedures. I guess I can move this step from pexpr.pas but this doesn't really affect the process of overloading, just the code is moved. That would apply to the dummy also, which would remain as ttypesym and then I would find all the generic procedures with the same name instead of adding them in pdecsub.pas. Again, this is just moving the code but it doesn't change the logic or process.

Please confirm this.

Sven Barth

2020-10-25 21:37

manager   ~0126555

In pexpr you'll either get the dummy symbol that was created when the generic functions were initially parsed or some other routine. generate_specialization_phase1 won't be called, because so specialization indicator ("specialize", "<") is found. Then through do_proc_call the code from tcallcandidates will be invoked. Inside that code you'll need to check if you can additionally add on-the-fly generated specializations of generic routines with the same symbol name to the overload list. The overload list then picks one (with a preference for non-specialized routines) and then the non picked specializations are discarded again.

Ryan Joseph

2020-10-25 21:51

reporter   ~0126556

What my code did was essentially calling generate_specialization_phase1() via try_implicit_specialization() and then following the normal code path to a tcallnode. This had less side effects in my opinion and fits with how the specialization process works normally but maybe I'm missing some other benefit.

What I'm going to do now is pass in the dummy sym and a nil tspecializationcontext to the tcallnode. once we reach tcallcandidates I will perform the try_implicit_specialization() step and acquire the tspecializationcontext.

I haven't tried this yet but I assume it's going to require some refactoring so that a ttypesym will be accepted since currently it requires a tprocsym. That's why I switched the dummy type because it reduced friction along the rest of the code path. Are you sure we want to keep the typesym approach? If yes then I will try.

Ryan Joseph

2020-10-29 19:08

reporter   ~0126645

Sven, after some tests I found that I can indeed move try_implicit_specialization into tcallcandidates.collect_overloads_in_units but I question why/how we should keep the dummy sym as a type sym and not not add generic candidates procdefs to it. Please answer these questions for me:

1) tcallnode and tcallcandidates assumes a tprocsym. This is a major assumption that will cause a serious reactor if we allow it to handle both types. Please review this and make sure that's what you really want but I just can't see it and I'd hate to make a huge mess trying to juggle these 2 types around.

2) I added the generic candidates to the proc dummy sym because then when I go to search for possible candidates I don't need to do a name search which basically does DOTHIS$1, DOTHIS$2, DOTHIS$3, DOTHIS$4, etc... There's no way to know how many templated variations of the function name exist so we need to do a blind search. I remember now that's why I did it like this.

Sven Barth

2021-01-31 17:47

manager   ~0128711

The dummy symbol was originally made a typesym, because Delphi allows code like this (and this way a new dummy type would not need to be introduced):

type
  TTest = class
  public type
    TFoo<T> = record
    end;
  public
    procedure TFoo;
  end;


Same is also true if a constant or a variable is involved which don't work yet in FPC, but the case with a generic type and a non generic routine does work.

The other way round, with a generic routine and a non generic type does not work however as it turns out (except for the edge case of both being generic... 🙄), probably exactly to allow implicit specializations. Thus I've changed the dummy symbol in r48002 for procsyms to be a a procsym as well, disallowing code like the following:

type
  TTest = class
  public type
    TFoo = record
    end;
  public
    procedure TFoo<T>;
  end;


That solves one problem you had. Now you should in most cases get a procsym that will result in htypechk becoming involved (and if something else is found then you're not supposed to call a function anyway, cause another symbol is in scope).

The reason for not modifying the dummy symbols is because you do not modify symbols (or defs) that might come from another unit. If we ever want to support multi threaded compilation this is a no-go. And even if there should be code that does that such a thing currently, it's no reason to add new code.

Nevertheless recognizing your problem I've added a new list genprocsymovlds to tprocsym in r48096 which keeps track of all generic overloads a non-generic or dummy procsym has. This way you should be able to find all suitable procsyms for a given symbol.

Thus the (simplified) approach should now be the following:
- in htypechk.tcallcandidates.collect_overloads_in_struct and .collect_overloads_in_unit you check whether the symbol also has generic procsyms as overloads
- for each of the procdefs of these generic symbols you try whether you can generate a suitable specialization for the routine's header (essentially what your pgenutil.try_implicit_specialization does)
- you add the specialization to the list of overloads
- let the overload handling of tcallcandidates do its job
- if a non-generic and one or multiple specializations are considered suitable, prefer the non-generic (and clean up the unused specializations); if a specialization is picked, "finalize" the specialization.

Logically no code outside the control flow of tcallcandidates should be needed this way.

Ryan Joseph

2021-01-31 22:33

reporter   ~0128718

Thanks Sven. I haven't looked at the code in a while but it sounds like you found a better way to keep the overloads then the list I was adding to the dummy sym (point 0000002 in my previous note). I'll need to study the code again and try to implement your changes. Sorry can't comment more now until I can actually remember how this works. :)

Ryan Joseph

2021-01-31 23:14

reporter   ~0128719

First minor question I have. In tspecializationcontext I have a list named "genericdeflist" but I see it's named "paramlist" now. Do you know why the name is different between our versions? I don't see why I would rename that unless I added it myself.

Sven Barth

2021-02-01 09:38

manager   ~0128721

It was renamed by you as part of the support for constant generic parameters as it can now not only contain defs, but also syms ;)

Ryan Joseph

2021-02-01 22:19

reporter   ~0128732

I integrated my changes into your new dummy sym but I get internal error 2014050904 now, which wasn't happening before. When it hits generate_specialization_phase2 and insert_generic_parameter_types it looks like the symbol added is of type "abstractsym" instead of type or const. Here's the branch I'm testing: https://github.com/genericptr/freepascal/tree/generic_implicit_3.

There were so many changes between my last patch (including gen consts) that it may take some time to figure out what happened.

Ryan Joseph

2021-02-02 20:36

reporter   ~0128746

Sven please look at this quickly when you have time. It's still got comments and junk in it but is the logic correct? I integrated the most recent trunk with your proc dummy sym, which was basically what I had before actually so it was easy to merge. It's still messy but I believe you should be able to apply this and do some basic tests.
patch.diff (27,172 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..6278b6b931 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2466,10 +2466,23 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                       (
+                         (m_implicit_function_specialization in current_settings.modeswitches) and 
+                         (sp_generic_dummy in srsym.symoptions)
+                       )
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if (m_implicit_function_specialization in current_settings.modeswitches) and 
+                      (sp_generic_dummy in srsym.symoptions) then
+                      begin
+                        writeln('attempt implicit spez of dummy');
+                        if not try_implicit_specialization(srsym,FParaNode,spezcontext) then
+                          writeln('implicit specialization failed');
+                      end;
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
@@ -2791,7 +2804,14 @@ 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 }
             if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
@@ -3285,7 +3305,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
@@ -3340,6 +3360,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 4bf6208858..2453a57a25 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
@@ -3605,6 +3604,7 @@ implementation
         statements : tstatementnode;
         converted_result_data : ttempcreatenode;
         calltype: tdispcalltype;
+        pdef: tdef;
       begin
          result:=nil;
          candidates:=nil;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..5d0a510a75 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
@@ -2888,6 +2887,7 @@ implementation
            tokenpos: tfileposinfo;
            spezcontext : tspecializationcontext;
            cufflags : tconsume_unitsym_flags;
+           origsym: tsym;
          begin
            { allow post fix operators }
            again:=true;
@@ -3079,13 +3079,25 @@ implementation
                      )
                    ) then
                  begin
+                   origsym:=srsym;
                    srsym:=resolve_generic_dummysym(srsym.name);
                    if assigned(srsym) then
                      srsymtable:=srsym.owner
                    else
                      begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
+                       // note(ryan): do we still need this with svens changes?
+                       if (sp_generic_dummy in origsym.symoptions) and 
+                         (m_implicit_function_specialization in current_settings.modeswitches) then
+                         begin
+                           internalerror(666);
+                           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 4804995dbb..5b218416b1 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;
@@ -63,14 +66,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +85,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +503,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +576,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -606,6 +613,313 @@ 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: ttypesym;
+          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));
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,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;
+          templates:=nil;
+          { 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 using 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 constrained 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: tfpobjectlist;
+        dummysym: tprocsym;
+        templates,
+        candidate_templates: tfphashlist;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        candidate:=nil;
+        templates:=nil;
+        candidate_templates:=nil;
+        dummysym:=tprocsym(sym);
+        candidates:=dummysym.genprocsymovlds;
+
+        { 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;
+                    candidate_templates:=templates;
+                  end;
+              end;
+          end;
+        if assigned(candidate) then
+          begin
+            writeln('found candidate ',candidate.typename,' for specialization');
+            generate_implicit_specialization(spezcontext,candidate,candidate_templates);
+            dummysym.ProcdefList.add(candidate);
+            result:=true;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+          freeandnil(candidate_templates);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..adca31f2da 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -1097,7 +1097,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..ed59c59bbe 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,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).genprocsymovlds) then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[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.diff (27,172 bytes)   

Ryan Joseph

2021-02-02 22:40

reporter   ~0128749

sorry, use this patch instead. I added object members also (not well tested yet but the logic works).
patch-2.diff (26,879 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..bcb264b228 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2244,6 +2244,12 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          if srsym.could_be_implicitly_specialized then
+            begin
+              writeln('attempt implicit spez of dummy');
+              if not try_implicit_specialization(srsym,FParaNode,spezcontext) then
+                exit;
+            end;
           for j:=0 to srsym.ProcdefList.Count-1 do
             begin
               pd:=tprocdef(srsym.ProcdefList[j]);
@@ -2466,10 +2472,19 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                     (sp_generic_dummy in srsym.symoptions)
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if tprocsym(srsym).could_be_implicitly_specialized then
+                      begin
+                        writeln('attempt implicit spez of dummy');
+                        if not try_implicit_specialization(srsym,FParaNode,spezcontext) then
+                          break;
+                      end;
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
@@ -2791,7 +2806,14 @@ 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 }
             if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
@@ -3340,6 +3362,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 4bf6208858..65bb3ddcc5 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..6460d620df 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
@@ -3084,8 +3083,8 @@ implementation
                      srsymtable:=srsym.owner
                    else
                      begin
-                       srsymtable:=nil;
-                       wasgenericdummy:=true;
+                      srsymtable:=nil;
+                      wasgenericdummy:=true;
                      end;
                  end;
 
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..5b218416b1 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;
@@ -63,14 +66,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +85,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +503,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +576,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -606,6 +613,313 @@ 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: ttypesym;
+          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));
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,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;
+          templates:=nil;
+          { 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 using 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 constrained 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: tfpobjectlist;
+        dummysym: tprocsym;
+        templates,
+        candidate_templates: tfphashlist;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        candidate:=nil;
+        templates:=nil;
+        candidate_templates:=nil;
+        dummysym:=tprocsym(sym);
+        candidates:=dummysym.genprocsymovlds;
+
+        { 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;
+                    candidate_templates:=templates;
+                  end;
+              end;
+          end;
+        if assigned(candidate) then
+          begin
+            writeln('found candidate ',candidate.typename,' for specialization');
+            generate_implicit_specialization(spezcontext,candidate,candidate_templates);
+            dummysym.ProcdefList.add(candidate);
+            result:=true;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+          freeandnil(candidate_templates);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..1f0ec1ebf3 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -156,6 +156,7 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype;isexplicit:boolean):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           procedure add_generic_overload(sym:tprocsym);
+          function could_be_implicitly_specialized:boolean;inline;
           property ProcdefList:TFPObjectList read FProcdefList;
           { only valid if sp_generic_dummy is set and either an overload was
             added using add_generic_overload or this was loaded from a ppu }
@@ -1097,7 +1098,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
@@ -1475,7 +1475,14 @@ implementation
         genprocsymovlds.add(sym);
       end;
 
-
+    
+    function tprocsym.could_be_implicitly_specialized:boolean;
+      begin
+        result:=(m_implicit_function_specialization in current_settings.modeswitches) and 
+                (sp_generic_dummy in symoptions) and
+                assigned(genprocsymovlds);          
+      end;
+      
 {****************************************************************************
                                   TERRORSYM
 ****************************************************************************}
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..82185de8b8 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,18 @@ 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 tprocsym(sym).could_be_implicitly_specialized then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[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-2.diff (26,879 bytes)   

Sven Barth

2021-02-03 09:53

manager   ~0128753

Judging by your message I assume you fixed the problem with the internal error you mentioned? ;)

The part outside of your try_implicit_specialization definitely looks better now, though you still have a flaw in your logic: your code should not do any overload checking. None of this "we found multiple candidates" business. try_implicit_specialization receives the symbol, the parameters and the ProcdefOverloadList. Then for each generic symbol your code processes each procdef. For each procdef it decides if the provided parameters can be matched to a valid specialization and if so it adds them to the ProcdefOverloadList. If there are multiple that is not your problem, that's the job of tcallcandidates to figure out and it will complain and also (normally) list the candidates that it found.

A small problem is there nevertheless: each specialization you add needs to have its own tspecializationcontext as in theory it would be possible that different parameters are used. Thus you'll also need to supply a list to try_implicit_specialization where the specialization contexts can be stored and a mapping from procdef to specialization context. The spezcontext supplied to tcallcandidates should not be used here. You could use cclasses.TFPHashObjectList for that by using the procdef's Self value as a key string.

Ryan Joseph

2021-02-03 18:06

reporter   ~0128758

It looks like in try_implicit_specialization() I call generate_implicit_specialization() on only 1 candidate but I should be doing this for ALL possible candidates. This was vestigial of the older code which did more stuff in the parser level. Should be easy to change this.

Currently I just add to the dummy syms ProcdefList (and clear the list manually each time) but I could add directly to ProcdefOverloadList and call maybe_specialize() inside of try_implicit_specialization(). This would mean I don't need to keep each spezcontext because I only use it when calling maybe_specialize() anyways.

Ryan Joseph

2021-02-03 22:31

reporter   ~0128764

another issue if I change this design is, how do we know if there is an ambiguous specialization? For example these 2 functions will be specialized and added to the overload list now. Where do you want to capture and give an error? I made notes of talks we had on the mailing list that says we need this to trigger an explicit error and indeed C# does so much and I believe Swift as well.

once all the possible specializations are made we can compare the params for equality. Can we do this in try_implicit_specialization before the function exists or do we need to wait until later?

generic procedure Run<T>(a:T; b: word);

generic procedure Run<T>(a: word; b: T);

Run(1,1); // <----- this should fail with a hard error

Ryan Joseph

2021-02-03 23:07

reporter   ~0128765

I quickly banged this out on my lunch break. I think this is what you want. As per my last question ambiguous calls, it appears the overloading system fails with an error "can't determine which overload to call".
patch-3.diff (27,929 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..603c7e7d16 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2244,6 +2244,12 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          if srsym.could_be_implicitly_specialized then
+            begin
+              if not try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+                exit;
+              foundanything:=true;
+            end;
           for j:=0 to srsym.ProcdefList.Count-1 do
             begin
               pd:=tprocdef(srsym.ProcdefList[j]);
@@ -2466,10 +2472,18 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                     (sp_generic_dummy in srsym.symoptions)
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if tprocsym(srsym).could_be_implicitly_specialized then
+                      begin
+                        if not try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),hasoverload) then
+                          break;
+                      end;
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
@@ -2791,7 +2805,14 @@ 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 }
             if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
@@ -3340,6 +3361,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 4bf6208858..65bb3ddcc5 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..f1afe0d317 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..6b2c602468 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; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):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;
@@ -63,14 +66,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +85,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +503,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +576,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -606,6 +613,344 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function try_implicit_specialization(sym:tsym;para: tnode; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):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: ttypesym;
+          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));
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,true,'',context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { note(ryan): having a hard time making sense of short string constant arrays
+        this is here for testing until we figure this out. basically we need to infer
+        an array of char (shortstring) as a short string instead of an array type }
+      function is_any_array(def:tdef): boolean;
+        begin
+          result := is_zero_based_array(def) or
+                    is_open_array(def) or
+                    is_dynamic_array(def) or
+                    is_array_constructor(def) or
+                    is_variant_array(def) or
+                    is_array_of_const(def) or
+                    is_special_array(def) or
+                    is_normal_array(def) or
+                    is_packed_array(def) or
+                    is_packed_record_or_object(def) or
+                    is_chararray(def) or
+                    is_widechararray(def) or
+                    is_open_chararray(def) or
+                    is_open_widechararray(def);
+        end;
+
+      function is_constant_array(def:tdef): boolean;
+        begin
+          result := is_any_array(def) and not is_stringlike(def);
+        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.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;
+          templates:=nil;
+          { 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 using element types }
+              if is_constant_array(vardef) then
+                vardef:=tarraydef(vardef).elementdef;
+              if is_constant_array(paradef) then
+                paradef:=tarraydef(paradef).elementdef;
+              template:=vardef.typesym.name;
+              writeln('  ',i,'=',template,':',paradef.typesym.realname,' (',vardef.typ,':',paradef.typ,')');
+
+              { handle procvars }
+              // TODO: paradef must be the correct type
+              if (paradef.typ=procvardef) and 
+                (vardef.typ=procvardef) and 
+                handle_procvars(vardef, paradef) then
+                template:=vardef.typesym.name;
+
+              { the param type is not a generic template (or a constrained 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;
+
+      function try_specialize(var pd:tprocdef;spezcontext:tspecializationcontext): boolean;
+        var
+          def : tdef;
+        begin
+          result:=false;
+          if assigned(spezcontext) then
+            begin
+              { check whether the given parameters are compatible
+                to the def's constraints }
+              if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
+                exit;
+              def:=generate_specialization_phase2(spezcontext,pd,false,'');
+              case def.typ of
+                errordef:
+                  { do nothing }
+                  ;
+                procdef:
+                  pd:=tprocdef(def);
+                else
+                  internalerror(666);
+              end;
+            end;
+          result:=true;
+        end;
+
+      var
+        i,
+        def_index: integer;
+        srsym: tprocsym;
+        paramtypes: tfplist;
+        pd: tprocdef;
+        possible: boolean;
+        dummysym: tprocsym;
+        templates: tfphashlist;
+        spezcontext:tspecializationcontext;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        templates:=nil;
+        dummysym:=tprocsym(sym);
+
+        writeln('try_implicit_specialization...');
+
+        paramtypes:=make_param_list(sym,para);
+        for i:=0 to dummysym.genprocsymovlds.count-1 do
+          begin
+            srsym:=tprocsym(dummysym.genprocsymovlds[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
+                    writeln('  found candidate ',pd.typename,' for specialization');
+                    generate_implicit_specialization(spezcontext,pd,templates);
+                    if not try_specialize(pd,spezcontext) then
+                      continue;
+                    pdoverloadlist.add(pd);
+                    // TODO: free spezcontext 
+                    // TODO: free templates
+                    if po_overload in pd.procoptions then
+                      hasoverload:=true;
+                    { Store first procsym found }
+                    if not assigned(first_procsym) then
+                      first_procsym:=srsym;
+                    result:=true;
+                  end;
+              end;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..1f0ec1ebf3 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -156,6 +156,7 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype;isexplicit:boolean):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           procedure add_generic_overload(sym:tprocsym);
+          function could_be_implicitly_specialized:boolean;inline;
           property ProcdefList:TFPObjectList read FProcdefList;
           { only valid if sp_generic_dummy is set and either an overload was
             added using add_generic_overload or this was loaded from a ppu }
@@ -1097,7 +1098,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
@@ -1475,7 +1475,14 @@ implementation
         genprocsymovlds.add(sym);
       end;
 
-
+    
+    function tprocsym.could_be_implicitly_specialized:boolean;
+      begin
+        result:=(m_implicit_function_specialization in current_settings.modeswitches) and 
+                (sp_generic_dummy in symoptions) and
+                assigned(genprocsymovlds);          
+      end;
+      
 {****************************************************************************
                                   TERRORSYM
 ****************************************************************************}
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..82185de8b8 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,18 @@ 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 tprocsym(sym).could_be_implicitly_specialized then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[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.diff (27,929 bytes)   

Ryan Joseph

2021-02-04 04:03

reporter   ~0128768

I needed to make a change in is_better_candidate() to get the non-generic precedence rule to work. I'll work out the bugs later but is this general design finally correct now? I'm curious if what I did in is_better_candidate is ok also.
patch-4.diff (27,781 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..cdb65f5dfa 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -2244,6 +2244,12 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          if srsym.could_be_implicitly_specialized then
+            begin
+              if not try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+                exit;
+              foundanything:=true;
+            end;
           for j:=0 to srsym.ProcdefList.Count-1 do
             begin
               pd:=tprocdef(srsym.ProcdefList[j]);
@@ -2466,10 +2472,18 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                     (sp_generic_dummy in srsym.symoptions)
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if tprocsym(srsym).could_be_implicitly_specialized then
+                      begin
+                        if not try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),hasoverload) then
+                          break;
+                      end;
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
@@ -2791,7 +2805,14 @@ 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 }
             if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
@@ -3258,6 +3279,17 @@ implementation
       var
         res : integer;
       begin
+        { if the best proc def is non-generic than it takes precedence over
+          a generic specialization }
+        if (m_implicit_function_specialization in current_settings.modeswitches) and 
+          not currpd^.invalid and 
+          currpd^.data.is_specialization and 
+          not bestpd^.data.is_specialization then
+          begin
+            writeln('non-generic overload takes precedence.');
+            result:=-1;
+            exit;
+          end;
         {
           Return values:
             > 0 when currpd is better than bestpd
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index 4bf6208858..65bb3ddcc5 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..f1afe0d317 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..6b2c602468 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; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):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;
@@ -63,14 +66,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +85,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +503,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +576,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -606,6 +613,344 @@ uses
         generate_specialization(tt,parse_class_parent,_prettyname,nil,'',dummypos);
       end;
 
+    function try_implicit_specialization(sym:tsym;para: tnode; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):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: ttypesym;
+          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));
+              writeln('  ',i,':', template,'=',ttypesym(templates.find(template)).realname);
+              if assigned(poslist) then
+                begin
+                  new(parampos);
+                  parampos^:=tmpparampos;
+                  poslist.add(parampos);
+                end;
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,true,'',context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { note(ryan): having a hard time making sense of short string constant arrays
+        this is here for testing until we figure this out. basically we need to infer
+        an array of char (shortstring) as a short string instead of an array type }
+      function is_any_array(def:tdef): boolean;
+        begin
+          result := is_zero_based_array(def) or
+                    is_open_array(def) or
+                    is_dynamic_array(def) or
+                    is_array_constructor(def) or
+                    is_variant_array(def) or
+                    is_array_of_const(def) or
+                    is_special_array(def) or
+                    is_normal_array(def) or
+                    is_packed_array(def) or
+                    is_packed_record_or_object(def) or
+                    is_chararray(def) or
+                    is_widechararray(def) or
+                    is_open_chararray(def) or
+                    is_open_widechararray(def);
+        end;
+
+      function is_constant_array(def:tdef): boolean;
+        begin
+          result := is_any_array(def) and not is_stringlike(def);
+        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.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;
+          templates:=nil;
+          { 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 using element types }
+              if is_constant_array(vardef) then
+                vardef:=tarraydef(vardef).elementdef;
+              if is_constant_array(paradef) then
+                paradef:=tarraydef(paradef).elementdef;
+              template:=vardef.typesym.name;
+              writeln('  ',i,'=',template,':',paradef.typesym.realname,' (',vardef.typ,':',paradef.typ,')');
+
+              { handle procvars }
+              // TODO: paradef must be the correct type
+              if (paradef.typ=procvardef) and 
+                (vardef.typ=procvardef) and 
+                handle_procvars(vardef, paradef) then
+                template:=vardef.typesym.name;
+
+              { the param type is not a generic template (or a constrained 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;
+
+      function try_specialize(var pd:tprocdef;spezcontext:tspecializationcontext): boolean;
+        var
+          def : tdef;
+        begin
+          result:=false;
+          if assigned(spezcontext) then
+            begin
+              { check whether the given parameters are compatible
+                to the def's constraints }
+              if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
+                exit;
+              def:=generate_specialization_phase2(spezcontext,pd,false,'');
+              case def.typ of
+                errordef:
+                  { do nothing }
+                  ;
+                procdef:
+                  pd:=tprocdef(def);
+                else
+                  internalerror(666);
+              end;
+            end;
+          result:=true;
+        end;
+
+      var
+        i,
+        def_index: integer;
+        srsym: tprocsym;
+        paramtypes: tfplist;
+        pd: tprocdef;
+        possible: boolean;
+        dummysym: tprocsym;
+        templates: tfphashlist;
+        spezcontext:tspecializationcontext;
+      label
+        finished;
+      begin
+        result:=false;
+        spezcontext:=nil;
+        templates:=nil;
+        dummysym:=tprocsym(sym);
+
+        writeln('try_implicit_specialization...');
+
+        paramtypes:=make_param_list(sym,para);
+        for i:=0 to dummysym.genprocsymovlds.count-1 do
+          begin
+            srsym:=tprocsym(dummysym.genprocsymovlds[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
+                    writeln('  found candidate ',pd.typename,' for specialization');
+                    generate_implicit_specialization(spezcontext,pd,templates);
+                    if not try_specialize(pd,spezcontext) then
+                      continue;
+                    pdoverloadlist.add(pd);
+                    // TODO: free spezcontext 
+                    // TODO: free templates
+                    if po_overload in pd.procoptions then
+                      hasoverload:=true;
+                    { Store first procsym found }
+                    if not assigned(first_procsym) then
+                      first_procsym:=srsym;
+                    result:=true;
+                  end;
+              end;
+          end;
+
+        finished:
+          freeandnil(paramtypes);
+      end;
 
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..1f0ec1ebf3 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -156,6 +156,7 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype;isexplicit:boolean):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           procedure add_generic_overload(sym:tprocsym);
+          function could_be_implicitly_specialized:boolean;inline;
           property ProcdefList:TFPObjectList read FProcdefList;
           { only valid if sp_generic_dummy is set and either an overload was
             added using add_generic_overload or this was loaded from a ppu }
@@ -1097,7 +1098,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
@@ -1475,7 +1475,14 @@ implementation
         genprocsymovlds.add(sym);
       end;
 
-
+    
+    function tprocsym.could_be_implicitly_specialized:boolean;
+      begin
+        result:=(m_implicit_function_specialization in current_settings.modeswitches) and 
+                (sp_generic_dummy in symoptions) and
+                assigned(genprocsymovlds);          
+      end;
+      
 {****************************************************************************
                                   TERRORSYM
 ****************************************************************************}
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..82185de8b8 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,18 @@ 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 tprocsym(sym).could_be_implicitly_specialized then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[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-4.diff (27,781 bytes)   

Sven Barth

2021-02-04 09:48

manager   ~0128770

I'll need to look at the code in more detail. I'll hopefully get back to you by the weekend at the latest.

Ryan Joseph

2021-02-05 02:19

reporter   ~0128778

great, thanks. Another lingering question I had: do we need to perform this expensive specialization process each time we encounter the dummy sym? In theory we should have the specialization cached but is captured at a higher level before we get into all my code? If not please suggest any ideas you have and I'll look into.

Sven Barth

2021-02-05 15:20

manager   ~0128784

The problem is that the resulting spezialization could be different for each set of parameters.

Let's get it working correctly first, then we can worry about performance...

Ryan Joseph

2021-02-05 17:27

reporter   ~0128788

Last edited: 2021-02-05 17:37

View 6 revisions

Sounds Good. Some points to consider when you get around to looking at this:

1) I didn't make any attempt to clean up any potential unused specializations. generate_specialization_phase2 is now called inside try_implicit_specialization so I think you mean to mark the tdef from generate_specialization_phase2() and free it later if call candidates didn't choose it.

2) I noticed you said earlier to "finalize" the specialization which is chosen. Not sure what you mean but things seem to work as they are right now so I assume this has already happened later downstream.

2) Since making the recent changes my check for generic precedence in is_better_candidate() seems to not be working now because is_better_candidate() is not called in some cases (like below). Not sure what happened here but I'll need to spend some time in the debugger to figure this out.

generic procedure DoThis<T>(a: T);
begin
end;

procedure DoThis(a: integer);
begin
end;

begin
    DoThis(integer(1));
end.


3) Open array params are not 100% yet. Working on that part still.

Sven Barth

2021-02-07 23:03

manager   ~0128815

Okay, I managed to take a look and got quite a couple remarks (not that surprising considering that I had avoided the code in pgenutil until we had the general structure correct):

- in pexpr and ncal there are still newline changes that can be undone
- in symtable.is_visible_for_object you should first have the non-generic check and then your check for the overloads

in htypechk.tcallcandidates:
- you shouldn't do an exit if try_implicit_specialization returned false, after all that only means that no generic candidate was found and might explain why your example with the non-generic overload does not work
- the changes around line 2791 aren't necessary anymore, as after your adjustments the spezcontext is Nil for implicit specializations anyway

in pgenutil.try_implicit_specialization:

- get rid of the "template"/"templates" notation; we're not using the C++ notation, we have generics and generic parameters, so name your parameters and variables accordingly, please
- don't generate type symbols for parameters that have no named type, generate it on the fly instead inside make_prettyname and use something else for housekeeping in your templates list (you're after all already using the pointer to the parameter if there is no type symbol)
- don't use gotos/labels if it can be avoided (and cleanup is not a reason for them)
- choose a better name for is_constant_array as I had a wrong understanding of it until I had taken a look at it
- you should be able to simply free the specialize context after the call to try_specialize, after all you're creating it for each specific generic procdef; same for the templates list

is_possible_specialization:
- what about default parameters of the tested generic function? (you're currently checking that the amount of passed parameters is equal to the non-hidden ones); if you don't want to tackle that now, that's fine as well, but we need to remark that when we provide the information about the feature
- you're handling only the element type in case of an array parameter; what about passing an array to a non-array generic parameter? (same remark as above if you don't want to handle that right now)
- don't use FreeAndNil(x); using x.Free alone is enough

handle_procvars:
- why do you use equality of the type parameter names if they should be the same typedef in case of inline specializations?
- what about generic procedure variables with multiple generic parameters? (your loop leaves after the first found parameter)
- what about generic procedure variables with generic parameters, e.g. generic procedure Run<T, U>(proc: specialize TProc<T, 20>, context: U)?

is_generic_template_compatible:
- rename to something like is_generic_param_used or uses_generic_param or so, the "compatible" part is confusing considering what the function checks
- use equality for the typesyms instead of their names
- a procvar parameter might not be a specialization, so checking genericparas will crash then

generate_implicit_specialization:
- add an internal error for the assumption that templates.count=genericdef.genericparas.count; it might be apparent if looking at the whole of try_implicit_specialization, but if only looking at single functions then it might not be
- it's not necessary to check whether poslist is assigned, cause it's always valid for a tspecializecontext

try_specialize:
- there is no need to check whether spezcontext is not Nil (though see next point)
- it might be better however to move tcallcandidates.maybe_specialize to pgenutil (maybe with a better name) to avoid code duplication

To answer your raised points (at least those that require an answer):

1) cleanup should already be done in tcallcandidates.destroy, so there should be nothing for you to do (though you might check whether anything not required is indeed cleaned up)

2) with "finalize" I essentially meant the try_specialize/maybe_specialize

Ryan Joseph

2021-02-07 23:16

reporter   ~0128816

Thanks for the remarks. We're getting close now. :) I'll address your individual points later once I start in on fixing things, but the one big question I could use some help with is, where to handle the situation of a non-generic taking precedence over the implicit specialization. I've been digging around in the debugger and it's not at all clear to me where this should happen. You can see I made some attempts in is_better_candidate() but this is not working in all situations. If you have insights into how tcallcandidates works please let me know what I should start looking.

Sven Barth

2021-02-08 09:59

manager   ~0128820

You should probably better determine this in tcallcandidates.get_information (extend tcandidate with an appropriate field) and then just do a simple check for that after the check for the ordinal distance (this way other differences will take precedence).

Ryan Joseph

2021-02-08 17:19

reporter   ~0128825

Some questions/remarks:

1) "don't generate type symbols for parameters that have no named type". is_possible_specialization and the params passed to generate_implicit_specialization rely upon their being a named typesym (this was inherited from the normal specialization process). tspecializationcontext.paramlist uses a typesym even so I don't see how we break this. Maybe there's a better way then creating a dummy, like using a type from the system unit?

2) "add an internal error for the assumption that templates.count=genericdef.genericparas.count". Please explain where you want this. This is the result of the function and is valid to be false if the params don't match (for now until I figure out default values).

3) I didn't consider default params at all. Unless it's trivial maybe it's good to hold off until the main code works since this feature has been getting held up for so many months now.

4) handle_procvars doesn't make perfect sense to me either. :) I may need to rewrite that whole thing. Sorry to have wasted your time but some of this code seemed to be tests/learning I was working out many months ago. The array stuff were tests also trying to figure out what to do with string literals since they appear as "arraydef" and I couldn't distinguish them from other arrays.

Sven Barth

2021-02-09 09:48

manager   ~0128834

Last edited: 2021-02-09 09:49

View 2 revisions

1) The way you're doing it now is definitely not good. Maybe for now better reject such implicit specializations and we'll think about it later on.

2) here in generate_implicit_specialization:

+          for i:=0 to templates.count-1 do
+            begin
+              template:=ttypesym(genericdef.genericparas[i]).name;


You're iterating templates, but access genericdef.genericparas. While the surrounding code ensures that this is correct while only reading generate_implicit_specialization that is not really obvious (and I had to read around quite a bit to ensure that it is indeed the case).

3) Agreed.

4) Maybe hold procvars back for now as well?

Let's get the basic and simple cases working first and then we add the more complicated ones.

Ryan Joseph

2021-02-09 18:08

reporter   ~0128838

For generic constants I made a function named create_generic_constsym because I must have ran into the same problem as we have here. The root problem is that specialization context param lists require a typesym and constant/literal types such as 'abc' and [1,2,3] aren't named. Correct? What options do we have besides providing a dummy type sym or doing a refactor so that tspecializationcontext.paramlist can accept tdef (big mess probably)?

Sven Barth

2021-02-10 09:38

manager   ~0128850

Maybe the more correct solution will be that the code that creates the anonymous def creates a type symbol as well. I'll have to take a look at that, so for now maybe better reject implicit specializations if a parameter has no type symbol until we've found a suitable solution for that.

Ryan Joseph

2021-02-10 15:40

reporter   ~0128866

That sounds reasonable. I made a little function as a placeholder because otherwise I can't test certain types but I'll work on the assumption that some symbol will exist eventually.

      function create_unamed_typesym(sym:tsym; def:tdef): tsym;
        var
          typename: string;
          newtype: tsym;
        begin
          newtype:=nil;
          if is_stringlike(def) then
            newtype:=search_system_type('SHORTSTRING')
          else if is_constant_array(def) then
            begin
              typename:=def.typename;
              newtype:=ctypesym.create(typename,def);
              newtype.owner:=def.owner;
            end
          else if def.typ=procvardef then
            begin
              // TODO: get name of function being pointed to
              // right now this generates $<address of procedure;Register> for plain functions
              typename:=def.typename;
              newtype:=ctypesym.create(typename,def);
              newtype.owner:=def.owner;
            end;
          if newtype=nil then
            internalerror(2021020904);
          def.typesym:=newtype;
        end;

Ryan Joseph

2021-02-15 20:43

reporter   ~0128943

I found this bug which causes a compiler crash. Is this worth trying to fix now? I don't even think we should try to implicitly specialize it because it's the order is ambiguous.


generic procedure DoThis<T, S>(p1: T; p2: S);
begin
end;

generic procedure DoThis<S, T>(p1: S; p2: T);
begin
end;

begin
    specialize DoThis<integer, char>(1, 'c');
end.

Sven Barth

2021-02-16 09:37

manager   ~0128948

Please report the crash separately.

Your code shouldn't need to do any checking here. It simply generates the implicit specializations and later on the candidate selection will simply complain about not being able to pick a overload (if there isn't any function that turns out to be more suitable, e.g. a non-generic one).

Later on we could for example improve this by giving functions that might have constraints better scores or something like that...

Ryan Joseph

2021-02-16 23:07

reporter   ~0128959

Last edited: 2021-02-17 09:50

View 5 revisions

I guess you could infer this by digging into the specialized types generic params but I don't see the reason to do this or the utility even. Probably opens up a whole can of worms on us also. Let me know if you think otherwise.

NOTE: angle brackets in TAnyClass are messing up for some reasons...

type
  generic TAnyClass = class
  end;

type
 TSomeClass = specialize TAnyClass<integer>;

generic procedure DoThis<T>(a: specialize TAnyClass<T>);
begin
end;

begin
    DoThis(TSomeClass.Create); // specialize DoThis<integer>(TSomeClass.Create);
end.

Ryan Joseph

2021-02-16 23:53

reporter   ~0128960

Last edited: 2021-02-16 23:56

View 2 revisions

I've cleaned up most the stuff you needed and redid the proc vars (very confusing). The big question left is what to do about create_unamed_typesym and perhaps the generic precedence stuff I did may not be right so please review that briefly. There are some TODO comments which you may have answers for because I was doing something questionable. If I'm basically on track then I'll start making tests.

Heres' a proc test to try. The idea is that TProc<S,V> is specialized with DoCallback params and then hoisted up to specialize Run<U,T>

    type
      generic TProc<S, V> = procedure(name: S; context: V);

    generic procedure Run<U, T>(proc: specialize TProc<U, T>; context: T);
    begin
      proc('TProc', context);
    end;

    procedure DoCallback(name: string; value: shortint);
    begin
      writeln(name, ' called ', value);
    end;

    begin
      Run(@DoCallback, 100);
    end.
patch-5.diff (35,274 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..72fe60725e 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -85,7 +85,6 @@ interface
         procedure create_candidate_list(ignorevisibility,allowdefaultparas,objcidcall,explicitunit,searchhelpers,anoninherited:boolean;spezcontext:tspecializationcontext);
         procedure calc_distance(st_root:tsymtable;objcidcall: boolean);
         function  proc_add(st:tsymtable;pd:tprocdef;objcidcall: boolean):pcandidate;
-        function  maybe_specialize(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
       public
         constructor create(sym:tprocsym;st:TSymtable;ppn:tnode;ignorevisibility,allowdefaultparas,objcidcall,explicitunit,searchhelpers,anoninherited:boolean;spezcontext:tspecializationcontext);
         constructor create_operator(op:ttoken;ppn:tnode);
@@ -2244,10 +2243,15 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          if srsym.could_be_implicitly_specialized then
+            begin
+              try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result);
+              foundanything:=true;
+            end;
           for j:=0 to srsym.ProcdefList.Count-1 do
             begin
               pd:=tprocdef(srsym.ProcdefList[j]);
-              if not maybe_specialize(pd,spezcontext) then
+              if not finalize_specialization(pd,spezcontext) then
                 continue;
               if (po_ignore_for_overload_resolution in pd.procoptions) then
                 begin
@@ -2466,14 +2470,19 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                     (sp_generic_dummy in srsym.symoptions)
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if tprocsym(srsym).could_be_implicitly_specialized then
+                      try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),hasoverload);
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
-                        if not maybe_specialize(pd,spezcontext) then
+                        if not finalize_specialization(pd,spezcontext) then
                           continue;
                         if (po_ignore_for_overload_resolution in pd.procoptions) then
                           begin
@@ -2783,33 +2792,6 @@ implementation
       end;
 
 
-    function tcallcandidates.maybe_specialize(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
-      var
-        def : tdef;
-      begin
-        result:=false;
-        if assigned(spezcontext) then
-          begin
-            if not (df_generic in pd.defoptions) then
-              internalerror(2015060301);
-            { check whether the given parameters are compatible
-              to the def's constraints }
-            if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
-              exit;
-            def:=generate_specialization_phase2(spezcontext,pd,false,'');
-            case def.typ of
-              errordef:
-                { do nothing }
-                ;
-              procdef:
-                pd:=tprocdef(def);
-              else
-                internalerror(2015070303);
-            end;
-          end;
-        result:=true;
-      end;
-
     procedure tcallcandidates.list(all:boolean);
       var
         hp : pcandidate;
@@ -3258,6 +3240,23 @@ implementation
       var
         res : integer;
       begin
+        writeln('*** is_better_candidate');
+        { 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('  currpd:',currpd^.data.is_specialization,' bestpd:', bestpd^.data.is_specialization);
+            if (currpd^.data.is_specialization and not bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                exit(-1);
+              end
+            else if (not currpd^.data.is_specialization and bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                exit(1);
+              end;
+          end;
         {
           Return values:
             > 0 when currpd is better than bestpd
@@ -3610,6 +3609,13 @@ implementation
         if (compare_paras(pd^.data.paras,bestpd^.data.paras,cp_value_equal_const,cpoptions)>=te_equal) and
           (not(po_objc in bestpd^.data.procoptions) or (bestpd^.data.messageinf.str^=pd^.data.messageinf.str^)) then
           compare_by_old_sortout_check := 1; // bestpd was sorted out before patch
+        
+        { for implicit specializations non-generics should take precedence so
+          when comparing a specialization to a non-specialization mark as undecided
+          and it will be re-evaluated in is_better_candidate }
+        if (m_implicit_function_specialization in current_settings.modeswitches)
+          and (pd^.data.is_specialization <> bestpd^.data.is_specialization) then
+          compare_by_old_sortout_check := 0;
      end;
 
     function decide_restart(pd,bestpd:pcandidate) : boolean;
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index 4bf6208858..65bb3ddcc5 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..f1afe0d317 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..402b11f8e5 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,6 +33,8 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
 
@@ -52,6 +54,8 @@ uses
     procedure add_generic_dummysym(sym:tsym);
     function resolve_generic_dummysym(const name:tidstring):tsym;
     function could_be_generic(const name:tidstring):boolean;inline;
+    function try_implicit_specialization(sym:tsym;para: tnode; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):boolean;
+    function finalize_specialization(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
 
     procedure generate_specialization_procs;
     procedure maybe_add_pending_specialization(def:tdef);
@@ -63,14 +67,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +86,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +504,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +577,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -607,6 +615,470 @@ uses
       end;
 
 
+    function finalize_specialization(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
+      var
+        def : tdef;
+      begin
+        result:=false;
+        if assigned(spezcontext) then
+          begin
+            if not (df_generic in pd.defoptions) then
+              internalerror(2015060301);
+            { check whether the given parameters are compatible
+              to the def's constraints }
+            if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
+              exit;
+            def:=generate_specialization_phase2(spezcontext,pd,false,'');
+            case def.typ of
+              errordef:
+                { do nothing }
+                ;
+              procdef:
+                pd:=tprocdef(def);
+              else
+                internalerror(2015070303);
+            end;
+          end;
+        result:=true;
+      end;
+
+
+    function try_implicit_specialization(sym:tsym;para:tnode;pdoverloadlist:tfpobjectlist;var first_procsym:tsym;var hasoverload:boolean):boolean;
+
+
+      { hash key for generic parameter lookups }
+      function generic_param_hash(def:tdef): string; inline;
+        begin
+          result := def.typename;
+        end;
+
+      function is_any_array(def:tdef): boolean;
+        begin
+          result := is_zero_based_array(def) or
+                    is_open_array(def) or
+                    is_dynamic_array(def) or
+                    is_array_constructor(def) or
+                    is_variant_array(def) or
+                    is_array_of_const(def) or
+                    is_special_array(def) or
+                    is_normal_array(def) or
+                    is_packed_array(def) or
+                    is_packed_record_or_object(def) or
+                    is_chararray(def) or
+                    is_widechararray(def) or
+                    is_open_chararray(def) or
+                    is_open_widechararray(def);
+        end;
+
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := is_any_array(def) and not is_stringlike(def);
+        end;
+
+      { makes the specialization context from the generic proc def and generic params }
+      procedure generate_implicit_specialization(out context:tspecializationcontext;genericdef:tprocdef;genericparams:tfphashlist);
+        var
+          parsedpos:tfileposinfo;
+          poslist:tfplist;
+          i: longint;
+          paramtype: ttypesym;
+          parampos : pfileposinfo;
+          tmpparampos : tfileposinfo;
+          paramname: string;
+        begin
+          context:=tspecializationcontext.create;
+          fillchar(parsedpos,sizeof(parsedpos),0);
+          poslist:=context.poslist;
+          tmpparampos:=current_filepos;
+          if genericparams.count<>genericdef.genericparas.count then
+            internalerror(2021020901);
+          for i:=0 to genericparams.count-1 do
+            begin
+              paramname:=generic_param_hash(ttypesym(genericdef.genericparas[i]).typedef);
+              paramtype:=ttypesym(genericparams.find(paramname));
+              if paramtype=nil then
+                internalerror(2021020902);
+              writeln('  ',i,': ', paramname,' = ',ttypesym(genericparams.find(paramname)).realname);
+              new(parampos);
+              parampos^:=tmpparampos;
+              poslist.add(parampos);
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,true,'',context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { specialization context parameter lists require a tsym so we need
+        to generate a placeholder for unnamed constant types like
+        short strings, open arrays, function pointers etc... }
+
+      function create_unamed_typesym(sym:tsym;def:tdef): tsym;
+        var
+          typename: string;
+          newtype: tsym;
+        begin
+          newtype:=nil;
+          if is_stringlike(def) then
+            newtype:=search_system_type('SHORTSTRING')
+          else if def.typ=procvardef then
+            begin
+              // TODO: get name of function being pointed to
+              // right now this generates $<address of procedure;Register>
+              typename:=def.typename;
+              newtype:=ctypesym.create(typename,def);
+              newtype.owner:=def.owner;
+            end
+          else
+            begin
+              typename:=def.typename;
+              newtype:=ctypesym.create(typename,def);
+              newtype.owner:=def.owner;
+            end;
+          if newtype=nil then
+            internalerror(2021020904);
+          def.typesym:=newtype;
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(dummysym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paraindex: integer;
+          paramtypes: tfplist;
+          paradef:tdef;
+          i:integer;
+        begin
+          paramtypes:=tfplist.create;
+          pt:=tcallparanode(para);
+          paraindex:=0;
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { unnamed parameter types can not be specialized }
+              if paradef.typesym=nil then
+                create_unamed_typesym(dummysym,paradef);
+              paramtypes.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+              paraindex:=paraindex+1;
+            end;
+          writeln('caller params:');
+          for i:=0 to paramtypes.count-1 do
+            writeln('  ',i,'=',ttypesym(paramtypes[i]).realname);
+          result:=paramtypes;
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          pd: tprocvardef;
+          parasym: ttypesym;
+          elementdef: tdef;
+          k, i: integer;
+        begin
+          result:=false;
+          for k:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[k]);
+              
+              { handle array types by using element types }
+              if paravar.vardef.typ=arraydef then
+                begin
+                  elementdef:=tarraydef(paravar.vardef).elementdef;
+                  if elementdef.typesym=nil then
+                    internalerror(2021020906);
+                  result:=(genericparam=elementdef.typesym) and (elementdef.typ=undefineddef);
+                end
+              { for procvar check if the generic param is used in one of the generic params }
+              else if paravar.vardef.typ=procvardef then
+                begin
+                  pd:=tprocvardef(paravar.vardef);
+                  if assigned(pd.genericparas) then
+                    for i:=0 to pd.genericparas.count-1 do
+                      begin
+                        parasym:=ttypesym(pd.genericparas[i]);
+                        if parasym.typedef.typesym=nil then
+                          internalerror(2021020907);
+                        if (genericparam=parasym.typedef.typesym) and (parasym.typedef.typ=undefineddef) then
+                          begin
+                            result:=true;
+                            break;
+                          end;
+                      end;
+                end
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam.name=paravar.vardef.typesym.name) and (paravar.vardef.typ=undefineddef);
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+      
+      { infer type from procvar parameters }
+      function handle_procvars(genericparams:tfphashlist;callerparams:tfplist;genericdef:tprocdef;target_def:tdef;caller_def:tdef): boolean;
+        var
+          i,j: integer;
+          paravar: tparavarsym;
+          target_proc,
+          caller_proc: tprocvardef;
+          target_proc_para,
+          caller_proc_para: tparavarsym;
+          key: string;
+          index: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match }
+          // TODO: consider default values?
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { make sure both sets of parameters are compatible first }
+          for i:=0 to target_proc.paras.count-1 do
+            begin
+              target_proc_para:=tparavarsym(target_proc.paras[i]);
+              caller_proc_para:=tparavarsym(caller_proc.paras[i]);
+
+              { the parameters are not compatible }
+              if compare_defs(caller_proc_para.vardef,target_proc_para.vardef,nothingn)=te_incompatible then
+                exit(false);
+
+              if target_proc_para.vardef.typ=undefineddef then
+                begin
+                  paravar:=tparavarsym(tprocvardef(target_proc.genericdef).paras[i]);
+
+                  { find the generic param name in the generic def parameters }
+                  j:=target_proc.genericdef.genericparas.findindexof(paravar.vardef.typesym.name);
+                  
+                  target_def:=tparavarsym(target_proc.paras[j]).vardef;
+                  caller_def:=caller_proc_para.vardef;
+
+                  if caller_def.typesym=nil then
+                    internalerror(2021020908);
+
+                  writeln('  ', i, ': ', target_proc_para.name, ' -> ', paravar.vardef.typename,': ', caller_def.typename, ' maps to ', target_def.typename);
+
+                  key:=generic_param_hash(target_def);
+
+                  { the generic param is already used }
+                  index:=genericparams.findindexof(key);
+                  if index<>-1 then
+                    continue;
+
+                  { add the type to the generic params }
+                  genericparams.add(key,caller_def.typesym);
+                  result:=true;
+                end;
+            end;
+        end;
+
+      { compare generic parameters <T> with call node parameters. }
+      function is_possible_specialization(callerparams:tfplist;genericdef:tprocdef;out genericparams:tfphashlist): boolean;
+        var
+          i, count: integer;
+          paravar: tparavarsym;
+          target_def,
+          caller_def: tdef;
+          key: string;
+          index: integer;
+          paras: tfplist;
+          elementdef: tdef;
+          required_param_count: integer;
+        label
+          finished;
+        begin
+          result:=false;
+          genericparams:=nil;
+          required_param_count:=0;
+
+          { first perform a check to reject generics with constants }
+          for i:=0 to genericdef.genericparas.count-1 do
+            if tsym(genericdef.genericparas[i]).typ=constsym then
+              goto finished;
+
+          { 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);
+
+              { count non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          writeln('► required: ',required_param_count, ' supplied: ', callerparams.count);
+
+          { not not enough parameters were supplied }
+          if callerparams.count<required_param_count then
+            goto finished;
+
+          { check to make sure the generic parameters are all used 
+            at least once in the  caller parameters. }
+          count:=0;
+          for i:=0 to genericdef.genericparas.count-1 do
+            if is_generic_param_used(ttypesym(genericdef.genericparas[i]), paras) then
+              inc(count);
+
+          writeln('► used generic params: ',count, ' required: ', genericdef.genericparas.count);
+          if count<genericdef.genericparas.count then
+            goto finished;
+
+          genericparams:=tfphashlist.create;
+          for i:=0 to callerparams.count-1 do
+            begin
+              caller_def:=ttypesym(callerparams[i]).typedef;
+
+              { caller parameter exceeded the possible parameters }
+              if i=paras.count then
+                goto finished;
+
+              target_def:=tparavarsym(paras[i]).vardef;
+              
+              { strings are compatible with "array of T" so we 
+                need to use the element type for specialization }
+              if is_stringlike(caller_def) and
+                is_array_literal(target_def) and
+                (tarraydef(target_def).elementdef.typ=undefineddef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  // TODO: are these char types correct?
+                  case tstringdef(caller_def).stringtype of
+                    st_shortstring,
+                    st_longstring,
+                    st_ansistring,
+                    st_unicodestring:
+                      caller_def:=cansichartype;
+                    st_widestring:
+                      caller_def:=cwidechartype;                      
+                  end;
+                end;
+
+              { if the generic param is "array of T" then use the
+                element type of the caller to specialize the open array }
+              if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                (tarraydef(target_def).elementdef.typ=undefineddef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  caller_def:=tarraydef(caller_def).elementdef;
+                end;
+                
+              { handle generic procvars specializations }
+              if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                assigned(tprocvardef(target_def).genericparas) and
+                handle_procvars(genericparams,callerparams,genericdef,target_def,caller_def) then
+                continue;
+
+              key:=generic_param_hash(target_def);
+              writeln('  ',i,': ',key,' -> ',caller_def.typesym.RealName);
+
+              { the param type is not a generic param (or a constrained type)
+                so the type needs to be checked }
+              if target_def.typ<>undefineddef then
+                begin
+                  if compare_defs(caller_def,target_def,nothingn)=te_incompatible then
+                    goto finished;
+                  { if the target_def has generic constraints then it is still 
+                    a generic param and needs to be added }
+                  if tstoreddef(target_def).genconstraintdata=nil then
+                    continue;
+                end;
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(key);
+              if index<>-1 then
+                continue;
+
+              { caller_def must have a named typesym for the spezcontext parameter list }
+              if caller_def.typesym=nil then
+                internalerror(2021020903);
+
+              genericparams.add(key,caller_def.typesym);
+            end;
+          
+          { if the parameter counts match then the specialization is possible }
+          result:=genericparams.count=genericdef.genericparas.count;
+          
+          finished:
+          if not result then
+            begin
+              genericparams.free;
+              paras.free;
+            end;
+        end;
+
+      var
+        i, j: integer;
+        srsym: tprocsym;
+        callerparams: tfplist;
+        pd: tprocdef;
+        possible: boolean;
+        dummysym: tprocsym;
+        genericparams: tfphashlist;
+        spezcontext:tspecializationcontext;
+      label
+        finished;
+      begin
+        writeln('try_implicit_specialization...');
+
+        result:=false;
+        spezcontext:=nil;
+        genericparams:=nil;
+        dummysym:=tprocsym(sym);
+        callerparams:=make_param_list(dummysym,para);
+        
+        { failed to build the parameter list }
+        if callerparams=nil then
+          exit(false);
+
+        for i:=0 to dummysym.genprocsymovlds.count-1 do
+          begin
+            srsym:=tprocsym(dummysym.genprocsymovlds[i]);
+            for j:=0 to srsym.ProcdefList.Count-1 do
+              begin
+                pd:=tprocdef(srsym.ProcdefList[j]);
+                writeln('try candidate: ',pd.fullprocname(true));
+                possible:=is_possible_specialization(callerparams,pd,genericparams);
+                if possible then
+                  begin
+                    writeln('  found candidate ',pd.typename,' for specialization');
+                    generate_implicit_specialization(spezcontext,pd,genericparams);
+                    genericparams.free;
+                    { finalize the specialization so it can be added to the list of overloads }
+                    if not finalize_specialization(pd,spezcontext) then
+                      begin
+                        spezcontext.free;
+                        continue;
+                      end;
+                    pdoverloadlist.add(pd);
+                    spezcontext.free;
+                    if po_overload in pd.procoptions then
+                      hasoverload:=true;
+                    { Store first procsym found }
+                    if not assigned(first_procsym) then
+                      first_procsym:=srsym;
+                    result:=true;
+                  end;
+              end;
+          end;
+
+        callerparams.free;
+      end;
+
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
         dummypos : tfileposinfo;
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..1f0ec1ebf3 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -156,6 +156,7 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype;isexplicit:boolean):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           procedure add_generic_overload(sym:tprocsym);
+          function could_be_implicitly_specialized:boolean;inline;
           property ProcdefList:TFPObjectList read FProcdefList;
           { only valid if sp_generic_dummy is set and either an overload was
             added using add_generic_overload or this was loaded from a ppu }
@@ -1097,7 +1098,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
@@ -1475,7 +1475,14 @@ implementation
         genprocsymovlds.add(sym);
       end;
 
-
+    
+    function tprocsym.could_be_implicitly_specialized:boolean;
+      begin
+        result:=(m_implicit_function_specialization in current_settings.modeswitches) and 
+                (sp_generic_dummy in symoptions) and
+                assigned(genprocsymovlds);          
+      end;
+      
 {****************************************************************************
                                   TERRORSYM
 ****************************************************************************}
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..9930f46fe8 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,8 @@ implementation
       begin
         if sym.typ=procsym then
           begin
-            { A procsym is visible, when there is at least one of the procdefs visible }
             result:=false;
+            { 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]);
@@ -3374,6 +3374,16 @@ implementation
                     exit;
                   end;
               end;
+            { check dummy sym visbility by following associated procsyms }
+            if tprocsym(sym).could_be_implicitly_specialized then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[i]),contextobjdef) then
+                    begin
+                      result:=true;
+                      exit;
+                    end;
+              end;
             if (tprocsym(sym).procdeflist.count=0) and (sp_generic_dummy in tprocsym(sym).symoptions) then
               result:=is_visible_for_object(sym.owner,sym.visibility,contextobjdef);
           end
patch-5.diff (35,274 bytes)   

Ryan Joseph

2021-02-21 18:58

reporter   ~0129067

I added class constraints (which I forget about before), removed the goto's and added some comments with your name (TODO(sven)) for places I had some additional questions.
patch-6.diff (37,358 bytes)   
diff --git a/compiler/globtype.pas b/compiler/globtype.pas
index c129d242b8..834da288d8 100644
--- a/compiler/globtype.pas
+++ b/compiler/globtype.pas
@@ -505,7 +505,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;
 
@@ -656,7 +657,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 }
@@ -697,7 +698,8 @@ interface
          'ARRAYOPERATORS',
          'MULTIHELPERS',
          'ARRAYTODYNARRAY',
-         'PREFIXEDATTRIBUTES'
+         'PREFIXEDATTRIBUTES',
+         'IMPLICITFUNCTIONSPECIALIZATION'
          );
 
 
diff --git a/compiler/htypechk.pas b/compiler/htypechk.pas
index 7e805b73e9..72fe60725e 100644
--- a/compiler/htypechk.pas
+++ b/compiler/htypechk.pas
@@ -85,7 +85,6 @@ interface
         procedure create_candidate_list(ignorevisibility,allowdefaultparas,objcidcall,explicitunit,searchhelpers,anoninherited:boolean;spezcontext:tspecializationcontext);
         procedure calc_distance(st_root:tsymtable;objcidcall: boolean);
         function  proc_add(st:tsymtable;pd:tprocdef;objcidcall: boolean):pcandidate;
-        function  maybe_specialize(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
       public
         constructor create(sym:tprocsym;st:TSymtable;ppn:tnode;ignorevisibility,allowdefaultparas,objcidcall,explicitunit,searchhelpers,anoninherited:boolean;spezcontext:tspecializationcontext);
         constructor create_operator(op:ttoken;ppn:tnode);
@@ -2244,10 +2243,15 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          if srsym.could_be_implicitly_specialized then
+            begin
+              try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result);
+              foundanything:=true;
+            end;
           for j:=0 to srsym.ProcdefList.Count-1 do
             begin
               pd:=tprocdef(srsym.ProcdefList[j]);
-              if not maybe_specialize(pd,spezcontext) then
+              if not finalize_specialization(pd,spezcontext) then
                 continue;
               if (po_ignore_for_overload_resolution in pd.procoptions) then
                 begin
@@ -2466,14 +2470,19 @@ implementation
                 srsym:=tsym(srsymtable.FindWithHash(hashedid));
                 if assigned(srsym) and
                    (srsym.typ=procsym) and
-                   (tprocsym(srsym).procdeflist.count>0) then
+                   (
+                     (tprocsym(srsym).procdeflist.count>0) or 
+                     (sp_generic_dummy in srsym.symoptions)
+                   ) then
                   begin
                     { add all definitions }
                     hasoverload:=false;
+                    if tprocsym(srsym).could_be_implicitly_specialized then
+                      try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),hasoverload);
                     for j:=0 to tprocsym(srsym).ProcdefList.Count-1 do
                       begin
                         pd:=tprocdef(tprocsym(srsym).ProcdefList[j]);
-                        if not maybe_specialize(pd,spezcontext) then
+                        if not finalize_specialization(pd,spezcontext) then
                           continue;
                         if (po_ignore_for_overload_resolution in pd.procoptions) then
                           begin
@@ -2783,33 +2792,6 @@ implementation
       end;
 
 
-    function tcallcandidates.maybe_specialize(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
-      var
-        def : tdef;
-      begin
-        result:=false;
-        if assigned(spezcontext) then
-          begin
-            if not (df_generic in pd.defoptions) then
-              internalerror(2015060301);
-            { check whether the given parameters are compatible
-              to the def's constraints }
-            if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
-              exit;
-            def:=generate_specialization_phase2(spezcontext,pd,false,'');
-            case def.typ of
-              errordef:
-                { do nothing }
-                ;
-              procdef:
-                pd:=tprocdef(def);
-              else
-                internalerror(2015070303);
-            end;
-          end;
-        result:=true;
-      end;
-
     procedure tcallcandidates.list(all:boolean);
       var
         hp : pcandidate;
@@ -3258,6 +3240,23 @@ implementation
       var
         res : integer;
       begin
+        writeln('*** is_better_candidate');
+        { 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('  currpd:',currpd^.data.is_specialization,' bestpd:', bestpd^.data.is_specialization);
+            if (currpd^.data.is_specialization and not bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                exit(-1);
+              end
+            else if (not currpd^.data.is_specialization and bestpd^.data.is_specialization) then
+              begin
+                writeln(currpd^.data.typename,' takes precedence because it''s non-generic.');
+                exit(1);
+              end;
+          end;
         {
           Return values:
             > 0 when currpd is better than bestpd
@@ -3610,6 +3609,13 @@ implementation
         if (compare_paras(pd^.data.paras,bestpd^.data.paras,cp_value_equal_const,cpoptions)>=te_equal) and
           (not(po_objc in bestpd^.data.procoptions) or (bestpd^.data.messageinf.str^=pd^.data.messageinf.str^)) then
           compare_by_old_sortout_check := 1; // bestpd was sorted out before patch
+        
+        { for implicit specializations non-generics should take precedence so
+          when comparing a specialization to a non-specialization mark as undecided
+          and it will be re-evaluated in is_better_candidate }
+        if (m_implicit_function_specialization in current_settings.modeswitches)
+          and (pd^.data.is_specialization <> bestpd^.data.is_specialization) then
+          compare_by_old_sortout_check := 0;
      end;
 
     function decide_restart(pd,bestpd:pcandidate) : boolean;
diff --git a/compiler/ncal.pas b/compiler/ncal.pas
index 4bf6208858..65bb3ddcc5 100644
--- a/compiler/ncal.pas
+++ b/compiler/ncal.pas
@@ -3576,7 +3576,6 @@ implementation
           end;
       end;
 
-
     function tcallnode.pass_typecheck:tnode;
 
       function is_undefined_recursive(def:tdef):boolean;
diff --git a/compiler/pexpr.pas b/compiler/pexpr.pas
index a36bef1900..f1afe0d317 100644
--- a/compiler/pexpr.pas
+++ b/compiler/pexpr.pas
@@ -1000,7 +1000,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
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..4688ce547b 100644
--- a/compiler/pgenutil.pas
+++ b/compiler/pgenutil.pas
@@ -33,6 +33,8 @@ uses
   globtype,
   { parser }
   pgentype,
+  { node }
+  node,
   { symtable }
   symtype,symdef,symbase;
 
@@ -52,6 +54,8 @@ uses
     procedure add_generic_dummysym(sym:tsym);
     function resolve_generic_dummysym(const name:tidstring):tsym;
     function could_be_generic(const name:tidstring):boolean;inline;
+    function try_implicit_specialization(sym:tsym;para: tnode; pdoverloadlist:tfpobjectlist; var first_procsym:tsym; var hasoverload:boolean):boolean;
+    function finalize_specialization(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
 
     procedure generate_specialization_procs;
     procedure maybe_add_pending_specialization(def:tdef);
@@ -63,14 +67,15 @@ implementation
 
 uses
   { common }
-  cutils,fpccrc,
+  cutils,fpccrc,sysutils,
   { global }
   globals,tokens,verbose,finput,constexp,
   { symtable }
   symconst,symsym,symtable,defcmp,defutil,procinfo,
   { modules }
   fmodule,
-  node,nobj,ncon,
+  { node }
+  nobj,ncon,ncal,
   { parser }
   scanner,
   pbase,pexpr,pdecsub,ptype,psub,pparautl;
@@ -81,6 +86,37 @@ uses
     tgeneric_param_const_types : tdeftypeset = [orddef,stringdef,floatdef,setdef,pointerdef,enumdef];
     tgeneric_param_nodes : tnodetypeset = [typen,ordconstn,stringconstn,realconstn,setconstn,niln];
 
+    procedure make_prettystring(paramtype:tdef;first:boolean;constprettyname:ansistring;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+',';
+        if constprettyname<>'' then
+          prettyname:=prettyname+constprettyname
+        else
+          prettyname:=prettyname+prettynamepart+paramtype.typesym.prettyname;
+      end;
+
     function get_generic_param_def(sym:tsym):tdef;
       begin
         if sym.typ=constsym then
@@ -468,14 +504,13 @@ uses
         parampos : pfileposinfo;
         tmpparampos : tfileposinfo;
         namepart : string;
-        prettynamepart : ansistring;
         module : tmodule;
         constprettyname : string;
         validparam : boolean;
       begin
         result:=true;
         prettyname:='';
-        prettynamepart:='';
+        constprettyname:='';
         if paramlist=nil then
           internalerror(2012061401);
         { set the block type to type, so that the parsed type are returned as
@@ -542,34 +577,7 @@ uses
                             constprettyname:='';
                             paramlist.Add(typeparam.resultdef.typesym);
                           end;
-                        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;
-                        if constprettyname<>'' then
-                          namepart:=namepart+'$$'+constprettyname;
-                        { we use the full name of the type to uniquely identify it }
-                        if typeparam.nodetype=typen then
-                          begin
-                            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;
-                          end;
-                        specializename:=specializename+namepart;
-                        if not first then
-                          prettyname:=prettyname+',';
-                        if constprettyname<>'' then
-                          prettyname:=prettyname+constprettyname
-                        else
-                          prettyname:=prettyname+prettynamepart+typeparam.resultdef.typesym.prettyname;
+                        make_prettystring(typeparam.resultdef,first,constprettyname,prettyname,specializename);
                       end;
                   end
                 else
@@ -607,6 +615,511 @@ uses
       end;
 
 
+    function finalize_specialization(var pd:tprocdef;spezcontext:tspecializationcontext):boolean;
+      var
+        def : tdef;
+      begin
+        result:=false;
+        if assigned(spezcontext) then
+          begin
+            if not (df_generic in pd.defoptions) then
+              internalerror(2015060301);
+            { check whether the given parameters are compatible
+              to the def's constraints }
+            if not check_generic_constraints(pd,spezcontext.paramlist,spezcontext.poslist) then
+              exit;
+            def:=generate_specialization_phase2(spezcontext,pd,false,'');
+            case def.typ of
+              errordef:
+                { do nothing }
+                ;
+              procdef:
+                pd:=tprocdef(def);
+              else
+                internalerror(2015070303);
+            end;
+          end;
+        result:=true;
+      end;
+
+
+    function try_implicit_specialization(sym:tsym;para:tnode;pdoverloadlist:tfpobjectlist;var first_procsym:tsym;var hasoverload:boolean):boolean;
+
+
+      { hash key for generic parameter lookups }
+      function generic_param_hash(def:tdef): string; inline;
+        begin
+          result := def.typename;
+        end;
+
+      { TODO(sven): if these names are sane do they belong in defutil.pas?
+        I could find no other way to know if a stringdef was not an array literal }
+      function is_any_array(def:tdef): boolean;
+        begin
+          result := is_zero_based_array(def) or
+                    is_open_array(def) or
+                    is_dynamic_array(def) or
+                    is_array_constructor(def) or
+                    is_variant_array(def) or
+                    is_array_of_const(def) or
+                    is_special_array(def) or
+                    is_normal_array(def) or
+                    is_packed_array(def) or
+                    is_packed_record_or_object(def) or
+                    is_chararray(def) or
+                    is_widechararray(def) or
+                    is_open_chararray(def) or
+                    is_open_widechararray(def);
+        end;
+
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := is_any_array(def) and not is_stringlike(def);
+        end;
+
+      { makes the specialization context from the generic proc def and generic params }
+      procedure generate_implicit_specialization(out context:tspecializationcontext;genericdef:tprocdef;genericparams:tfphashlist);
+        var
+          parsedpos:tfileposinfo;
+          poslist:tfplist;
+          i: longint;
+          paramtype: ttypesym;
+          parampos : pfileposinfo;
+          tmpparampos : tfileposinfo;
+          paramname: string;
+        begin
+          context:=tspecializationcontext.create;
+          fillchar(parsedpos,sizeof(parsedpos),0);
+          poslist:=context.poslist;
+          tmpparampos:=current_filepos;
+          if genericparams.count<>genericdef.genericparas.count then
+            internalerror(2021020901);
+          for i:=0 to genericparams.count-1 do
+            begin
+              paramname:=generic_param_hash(ttypesym(genericdef.genericparas[i]).typedef);
+              paramtype:=ttypesym(genericparams.find(paramname));
+              if paramtype=nil then
+                internalerror(2021020902);
+              writeln('  ',i,': ', paramname,' = ',ttypesym(genericparams.find(paramname)).realname);
+              new(parampos);
+              parampos^:=tmpparampos;
+              poslist.add(parampos);
+              context.paramlist.Add(paramtype);
+              make_prettystring(paramtype.typedef,i=0,'',context.prettyname,context.specializename);
+            end;
+          context.genname:=genericdef.procsym.realname;
+          writeln('generate_implicit_specialization:',context.genname,'<',context.prettyname,'>');
+        end;
+
+      { specialization context parameter lists require a tsym so we need
+        to generate a placeholder for unnamed constant types like
+        short strings, open arrays, function pointers etc... }
+
+      function create_unamed_typesym(sym:tsym;def:tdef): tsym;
+        var
+          typename: string;
+          newtype: tsym;
+        begin
+          newtype:=nil;
+          if is_stringlike(def) then
+            newtype:=search_system_type('SHORTSTRING')
+          else if def.typ=procvardef then
+            begin
+              // TODO: get name of function being pointed to
+              // right now this generates $<address of procedure;Register>
+              typename:=def.typename;
+              newtype:=ctypesym.create(typename,def);
+              newtype.owner:=def.owner;
+            end
+          else
+            begin
+              typename:=def.typename;
+              newtype:=ctypesym.create(typename,def);
+              newtype.owner:=def.owner;
+            end;
+          if newtype=nil then
+            internalerror(2021020904);
+          def.typesym:=newtype;
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(dummysym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paraindex: integer;
+          paramtypes: tfplist;
+          paradef:tdef;
+          i:integer;
+        begin
+          paramtypes:=tfplist.create;
+          pt:=tcallparanode(para);
+          paraindex:=0;
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { unnamed parameter types can not be specialized }
+              if paradef.typesym=nil then
+                create_unamed_typesym(dummysym,paradef);
+              paramtypes.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+              paraindex:=paraindex+1;
+            end;
+          writeln('caller params:');
+          for i:=0 to paramtypes.count-1 do
+            writeln('  ',i,'=',ttypesym(paramtypes[i]).realname);
+          result:=paramtypes;
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          pd: tprocvardef;
+          parasym: ttypesym;
+          elementdef: tdef;
+          k, i: integer;
+        begin
+          result:=false;
+          for k:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[k]);
+              
+              { handle array types by using element types }
+              if paravar.vardef.typ=arraydef then
+                begin
+                  elementdef:=tarraydef(paravar.vardef).elementdef;
+                  if elementdef.typesym=nil then
+                    internalerror(2021020906);
+                  result:=(genericparam=elementdef.typesym) and (elementdef.typ=undefineddef);
+                end
+              { for procvar check if the generic param is used in one of the generic params }
+              else if paravar.vardef.typ=procvardef then
+                begin
+                  pd:=tprocvardef(paravar.vardef);
+                  if assigned(pd.genericparas) then
+                    for i:=0 to pd.genericparas.count-1 do
+                      begin
+                        parasym:=ttypesym(pd.genericparas[i]);
+                        if parasym.typedef.typesym=nil then
+                          internalerror(2021020907);
+                        if (genericparam=parasym.typedef.typesym) and (parasym.typedef.typ=undefineddef) then
+                          begin
+                            result:=true;
+                            break;
+                          end;
+                      end;
+                end
+              { object types may be generic param with constraint }
+              { TODO(sven): should tstoredef have a method to test if it's generic param?
+                usually it's enough to check for typ=undefineddef but other types with
+                non-nil genconstraintdata may also be generic params }
+              else if paravar.vardef.typ=objectdef then
+                result:=(genericparam.name=paravar.vardef.typesym.name) and assigned(tstoreddef(paravar.vardef).genconstraintdata)
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam.name=paravar.vardef.typesym.name) and (paravar.vardef.typ=undefineddef);
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+      
+      { infer type from procvar parameters }
+      function handle_procvars(genericparams:tfphashlist;callerparams:tfplist;genericdef:tprocdef;target_def:tdef;caller_def:tdef): boolean;
+        var
+          i,j: integer;
+          paravar: tparavarsym;
+          target_proc,
+          caller_proc: tprocvardef;
+          target_proc_para,
+          caller_proc_para: tparavarsym;
+          key: string;
+          index: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match }
+          // TODO: consider default values?
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { make sure both sets of parameters are compatible first }
+          for i:=0 to target_proc.paras.count-1 do
+            begin
+              target_proc_para:=tparavarsym(target_proc.paras[i]);
+              caller_proc_para:=tparavarsym(caller_proc.paras[i]);
+
+              { the parameters are not compatible }
+              if compare_defs(caller_proc_para.vardef,target_proc_para.vardef,nothingn)=te_incompatible then
+                exit(false);
+
+              if target_proc_para.vardef.typ=undefineddef then
+                begin
+                  paravar:=tparavarsym(tprocvardef(target_proc.genericdef).paras[i]);
+
+                  { find the generic param name in the generic def parameters }
+                  j:=target_proc.genericdef.genericparas.findindexof(paravar.vardef.typesym.name);
+                  
+                  target_def:=tparavarsym(target_proc.paras[j]).vardef;
+                  caller_def:=caller_proc_para.vardef;
+
+                  if caller_def.typesym=nil then
+                    internalerror(2021020908);
+
+                  writeln('  ', i, ': ', target_proc_para.name, ' -> ', paravar.vardef.typename,': ', caller_def.typename, ' maps to ', target_def.typename);
+
+                  key:=generic_param_hash(target_def);
+
+                  { the generic param is already used }
+                  index:=genericparams.findindexof(key);
+                  if index<>-1 then
+                    continue;
+
+                  { add the type to the generic params }
+                  genericparams.add(key,caller_def.typesym);
+                  result:=true;
+                end;
+            end;
+        end;
+
+      { compare generic parameters <T> with call node parameters. }
+      function is_possible_specialization(callerparams:tfplist;genericdef:tprocdef;out genericparams:tfphashlist): boolean;
+        var
+          i,j,count: integer;
+          paravar: tparavarsym;
+          target_def,
+          caller_def: tdef;
+          target_key: string;
+          index: integer;
+          paras: tfplist;
+          elementdef: tdef;
+          required_param_count: integer;
+        begin
+          result:=false;
+          paras:=nil;
+          genericparams:=nil;
+          required_param_count:=0;
+
+          { first perform a check to reject generics with constants }
+          for i:=0 to genericdef.genericparas.count-1 do
+            if tsym(genericdef.genericparas[i]).typ=constsym then
+              exit;
+
+          { 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);
+
+              { count non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          writeln('► required: ',required_param_count, ' supplied: ', callerparams.count);
+
+          { not not enough parameters were supplied }
+          if callerparams.count<required_param_count then
+            begin
+              paras.free;
+              exit;
+            end;
+
+          { check to make sure the generic parameters are all used 
+            at least once in the  caller parameters. }
+          count:=0;
+          for i:=0 to genericdef.genericparas.count-1 do
+            if is_generic_param_used(ttypesym(genericdef.genericparas[i]), paras) then
+              inc(count);
+
+          writeln('► used generic params: ',count, ' required: ', genericdef.genericparas.count);
+          if count<genericdef.genericparas.count then
+            begin
+              paras.free;
+              exit;
+            end;
+
+          genericparams:=tfphashlist.create;
+          for i:=0 to callerparams.count-1 do
+            begin
+              caller_def:=ttypesym(callerparams[i]).typedef;
+
+              { caller parameter exceeded the possible parameters }
+              if i=paras.count then
+                begin
+                  genericparams.free;
+                  paras.free;
+                  exit;
+                end;
+
+              target_def:=tparavarsym(paras[i]).vardef;
+              target_key:='';
+
+              { strings are compatible with "array of T" so we 
+                need to use the element type for specialization }
+              if is_stringlike(caller_def) and
+                is_array_literal(target_def) and
+                (tarraydef(target_def).elementdef.typ=undefineddef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                  { find the char type which is compatible for the string element }
+                  { TODO(sven): are these char types correct?
+                    tstringdef may want to include these as a methods }
+                  case tstringdef(caller_def).stringtype of
+                    st_shortstring,
+                    st_longstring,
+                    st_ansistring,
+                    st_unicodestring:
+                      caller_def:=cansichartype;
+                    st_widestring:
+                      caller_def:=cwidechartype;                      
+                  end;
+                end;
+
+              { if the generic param is "array of T" then use the
+                element type of the caller to specialize the open array }
+              if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                (tarraydef(target_def).elementdef.typ=undefineddef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  caller_def:=tarraydef(caller_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                end;
+                
+              { handle generic procvars specializations }
+              if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                assigned(tprocvardef(target_def).genericparas) and
+                handle_procvars(genericparams,callerparams,genericdef,target_def,caller_def) then
+                continue;
+              
+              { handle objects with constraints by taking the base class
+                as the type to specialize }    
+              if (caller_def.typ=objectdef) and 
+                (target_def.typ=objectdef) and
+                (tobjectdef(target_def).genconstraintdata<>nil) then
+                begin
+                  target_key:=generic_param_hash(target_def);
+                  target_def:=tobjectdef(target_def).childof;
+                end;
+
+              { handle all other generic params }
+              if (target_key='') and (target_def.typ=undefineddef) then
+                target_key:=generic_param_hash(target_def);
+
+              writeln('  ',i,': ',target_key,' -> ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn));
+
+              { TODO(sven): compare_defs returns te_incompatible for TObject/Pointer
+                so the following will fail
+
+                  generic procedure DoThis<T: TMyClass>(obj: T; other: TObject);
+                  DoThis(TMyClass.Create, nil);  // <--- nil fails on compare_defs!
+              }
+
+              { the param type is not a generic param so the type needs to be checked }
+              if (target_def.typ<>undefineddef) and 
+                (compare_defs(caller_def,target_def,nothingn)=te_incompatible) then
+                begin
+                  genericparams.free;
+                  paras.free;
+                  exit;
+                end;
+
+              { the param doesn't have a generic key which means we don't need to consider it }
+              if target_key='' then
+                continue;
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(target_key);
+              if index<>-1 then
+                continue;
+
+              { caller_def must have a named typesym for the spezcontext parameter list }
+              if caller_def.typesym=nil then
+                internalerror(2021020903);
+
+              genericparams.add(target_key,caller_def.typesym);
+            end;
+          
+          { if the parameter counts match then the specialization is possible }
+          result:=genericparams.count=genericdef.genericparas.count;
+          
+          { cleanup }
+          if not result then
+            begin
+              genericparams.free;
+              paras.free;
+            end;
+        end;
+
+      var
+        i, j: integer;
+        srsym: tprocsym;
+        callerparams: tfplist;
+        pd: tprocdef;
+        possible: boolean;
+        dummysym: tprocsym;
+        genericparams: tfphashlist;
+        spezcontext:tspecializationcontext;
+      begin
+        writeln('try_implicit_specialization...');
+
+        result:=false;
+        spezcontext:=nil;
+        genericparams:=nil;
+        dummysym:=tprocsym(sym);
+        callerparams:=make_param_list(dummysym,para);
+        
+        { failed to build the parameter list }
+        if callerparams=nil then
+          exit;
+
+        for i:=0 to dummysym.genprocsymovlds.count-1 do
+          begin
+            srsym:=tprocsym(dummysym.genprocsymovlds[i]);
+            for j:=0 to srsym.ProcdefList.Count-1 do
+              begin
+                pd:=tprocdef(srsym.ProcdefList[j]);
+                writeln('try candidate: ',pd.fullprocname(true));
+                possible:=is_possible_specialization(callerparams,pd,genericparams);
+                if possible then
+                  begin
+                    writeln('  found candidate ',pd.typename,' for specialization');
+                    generate_implicit_specialization(spezcontext,pd,genericparams);
+                    genericparams.free;
+                    { finalize the specialization so it can be added to the list of overloads }
+                    if not finalize_specialization(pd,spezcontext) then
+                      begin
+                        spezcontext.free;
+                        continue;
+                      end;
+                    pdoverloadlist.add(pd);
+                    spezcontext.free;
+                    if po_overload in pd.procoptions then
+                      hasoverload:=true;
+                    { Store first procsym found }
+                    if not assigned(first_procsym) then
+                      first_procsym:=srsym;
+                    result:=true;
+                  end;
+              end;
+          end;
+
+        callerparams.free;
+      end;
+
     function generate_specialization_phase1(out context:tspecializationcontext;genericdef:tdef):tdef;
       var
         dummypos : tfileposinfo;
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..1f0ec1ebf3 100644
--- a/compiler/symsym.pas
+++ b/compiler/symsym.pas
@@ -156,6 +156,7 @@ interface
           function find_procdef_assignment_operator(fromdef,todef:tdef;var besteq:tequaltype;isexplicit:boolean):Tprocdef;
           function find_procdef_enumerator_operator(fromdef,todef:tdef;var besteq:tequaltype):Tprocdef;
           procedure add_generic_overload(sym:tprocsym);
+          function could_be_implicitly_specialized:boolean;inline;
           property ProcdefList:TFPObjectList read FProcdefList;
           { only valid if sp_generic_dummy is set and either an overload was
             added using add_generic_overload or this was loaded from a ppu }
@@ -1097,7 +1098,6 @@ implementation
           end;
       end;
 
-
     function Tprocsym.Find_procdef_bytype(pt:Tproctypeoption):Tprocdef;
       var
         i  : longint;
@@ -1475,7 +1475,14 @@ implementation
         genprocsymovlds.add(sym);
       end;
 
-
+    
+    function tprocsym.could_be_implicitly_specialized:boolean;
+      begin
+        result:=(m_implicit_function_specialization in current_settings.modeswitches) and 
+                (sp_generic_dummy in symoptions) and
+                assigned(genprocsymovlds);          
+      end;
+      
 {****************************************************************************
                                   TERRORSYM
 ****************************************************************************}
diff --git a/compiler/symtable.pas b/compiler/symtable.pas
index 8117af72f7..9930f46fe8 100644
--- a/compiler/symtable.pas
+++ b/compiler/symtable.pas
@@ -3362,8 +3362,8 @@ implementation
       begin
         if sym.typ=procsym then
           begin
-            { A procsym is visible, when there is at least one of the procdefs visible }
             result:=false;
+            { 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]);
@@ -3374,6 +3374,16 @@ implementation
                     exit;
                   end;
               end;
+            { check dummy sym visbility by following associated procsyms }
+            if tprocsym(sym).could_be_implicitly_specialized then
+              begin
+                for i:=0 to tprocsym(sym).genprocsymovlds.count-1 do
+                  if is_visible_for_object(tsym(tprocsym(sym).genprocsymovlds[i]),contextobjdef) then
+                    begin
+                      result:=true;
+                      exit;
+                    end;
+              end;
             if (tprocsym(sym).procdeflist.count=0) and (sp_generic_dummy in tprocsym(sym).symoptions) then
               result:=is_visible_for_object(sym.owner,sym.visibility,contextobjdef);
           end
patch-6.diff (37,358 bytes)   

Sven Barth

2021-02-22 09:50

manager   ~0129084

Will try to take a look this week, though in the worst case it could be till the weekend...

Ryan Joseph

2021-03-04 15:58

reporter   ~0129381

Just a reminder lest we forget. :) getting so close now. The 2 major points of interest are what to do with create_unamed_typesym and the overload precedence in htypechk.pas which I may have done something questionable in.

Sven Barth

2021-03-04 21:52

manager   ~0129386

I didn't have time at the weekend and yesterday I came around half way through. I hope to have my remarks at the upcoming weekend.

Sven Barth

2021-03-06 17:42

manager   ~0129442

Now finally my remarks:

First of regarding the type symbols: instead of creating a type sym instead check the locations where the type symbol is currently required and adjust them to work similar to tabstractprocdef.typename_paras at around line 5622 (where it checks for the type symbol and uses tdef.GetTypeName if there is none).

Now specific remarks:

htypechk:

processprocsym: foundanything should only be set to true if try_implicit_specialization did indeed generate something useful
is_better_candidate:
- you don't need to check for m_implicit_specializations, cause you'll have a mixture of specialization vs. non-specialization only in the case of implicit specializations
- your check should be the last as the other differences should take precendence, so place it at the last res:=0 instead
compare_by_old_sortout_check: you shouldn't need to change anything there

pgenutil.try_implicit_specialization:

is_any_array:
- why don't you simply check for def.typ=arraydef?
- the check for is_packed_record_or_object has no place in a function called is_any_array
make_param_list:
- why do you use a local variable paramtypes instead of simply using result?
- what is the use of paraindex?
is_generic_param_used:
- you can generalize the check for procvardefs for any type that's a specialization, cause there might be inline specializations in the declaration that aren't procvars (also it should probably be done recursively)
- you can simplify to check for generic parameters by checking that the owner is the procdef's parasymtable (in addition to checking for undefineddef and genconstraintdata to make sure that it's indeed a generic parameter and not some anonymous type)
- regarding your question/todo: the main designator is the symbol, not the def; you should be able to check whether the def of the type parameter is the same as the one your comparing to instead of comparing the names
handle_procvars:
- target_proc_para might be a constraint, so check target_proc_para.vardef.typesym for sp_generic_para instead of checking for an undefineddef
- don't check index<>-1, but index>=0
- you should set result only to true if you handled all parameters
is_possible_specialization:
- regarding your question: chartype of Unicodestring is Widechar
- you should use else to avoid called_def potentially being set multiple times
- don't use compare_defs for non-generic parameters, just skip them (overload handling will deal with them later on)
- don't check index<>-1, but index>=0
try_implicit_specialization:
- get rid of the possible variable

And now you should also provide quite a bunch of tests for this.

Ryan Joseph

2021-03-06 18:35

reporter   ~0129449

Last edited: 2021-03-06 19:18

View 2 revisions

Quick replies before I dig into this more:

The changes in compare_by_old_sortout_check are needed actually. I don't understand why exactly but I but I spent much time until I figured this out. It was also necessary for my to make my changes in is_better_candidate outside of the big "if" block. For that reason I decided to put the check at the very top because it must supercede any of the other logic anyways. Here's a test to try if want to experiment yourself. The explicit cast didn't work without changes to compare_by_old_sortout_check which I don't understand.

  procedure DoThis(para: integer);
  begin
    writeln('DoThis()');
  end;

  generic procedure DoThis<T>(para: T);
  begin
    writeln('DoThis<T>');
  end;

  DoThis(1);
  DoThis(integer(1));


I see in "tabstractprocdef.typename_paras" there is a nice template case statement for dealing with constant symbols (kind of what we need here) but the reason I want a typesym is because it's required to be in the list for tspecializationcontext. It's not that I can't get a representation of the constant value but if the function is specialized as an Integer because it got "1" as a parameter then tspecializationcontext.paramlist needs an "Integer" typesym for the rest of the specialization process. It's kind of like if we had implicit variables in pascal and we could do var i = 1; and the compiler would know this is "Integer", which is would get from the System unit I think...

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
2020-10-24 19:52 Sven Barth Note Added: 0126519
2020-10-24 19:52 Sven Barth Note Edited: 0126519 View Revisions
2020-10-24 19:53 Sven Barth Note Edited: 0126519 View Revisions
2020-10-24 20:08 Ryan Joseph Note Added: 0126520
2020-10-25 17:51 Ryan Joseph Note Added: 0126546
2020-10-25 21:37 Sven Barth Note Added: 0126555
2020-10-25 21:51 Ryan Joseph Note Added: 0126556
2020-10-29 19:08 Ryan Joseph Note Added: 0126645
2021-01-31 17:47 Sven Barth Note Added: 0128711
2021-01-31 22:33 Ryan Joseph Note Added: 0128718
2021-01-31 23:14 Ryan Joseph Note Added: 0128719
2021-02-01 09:38 Sven Barth Note Added: 0128721
2021-02-01 22:19 Ryan Joseph Note Added: 0128732
2021-02-02 20:36 Ryan Joseph Note Added: 0128746
2021-02-02 20:36 Ryan Joseph File Added: patch.diff
2021-02-02 22:40 Ryan Joseph Note Added: 0128749
2021-02-02 22:40 Ryan Joseph File Added: patch-2.diff
2021-02-03 09:53 Sven Barth Note Added: 0128753
2021-02-03 18:06 Ryan Joseph Note Added: 0128758
2021-02-03 22:31 Ryan Joseph Note Added: 0128764
2021-02-03 23:07 Ryan Joseph Note Added: 0128765
2021-02-03 23:07 Ryan Joseph File Added: patch-3.diff
2021-02-04 04:03 Ryan Joseph Note Added: 0128768
2021-02-04 04:03 Ryan Joseph File Added: patch-4.diff
2021-02-04 09:48 Sven Barth Note Added: 0128770
2021-02-05 02:19 Ryan Joseph Note Added: 0128778
2021-02-05 15:20 Sven Barth Note Added: 0128784
2021-02-05 17:27 Ryan Joseph Note Added: 0128788
2021-02-05 17:33 Ryan Joseph Note Edited: 0128788 View Revisions
2021-02-05 17:35 Ryan Joseph Note Edited: 0128788 View Revisions
2021-02-05 17:35 Ryan Joseph Note Edited: 0128788 View Revisions
2021-02-05 17:36 Ryan Joseph Note Edited: 0128788 View Revisions
2021-02-05 17:37 Ryan Joseph Note Edited: 0128788 View Revisions
2021-02-07 23:03 Sven Barth Note Added: 0128815
2021-02-07 23:16 Ryan Joseph Note Added: 0128816
2021-02-08 09:59 Sven Barth Note Added: 0128820
2021-02-08 17:19 Ryan Joseph Note Added: 0128825
2021-02-09 09:48 Sven Barth Note Added: 0128834
2021-02-09 09:49 Sven Barth Note Edited: 0128834 View Revisions
2021-02-09 18:08 Ryan Joseph Note Added: 0128838
2021-02-10 09:38 Sven Barth Note Added: 0128850
2021-02-10 15:40 Ryan Joseph Note Added: 0128866
2021-02-15 20:43 Ryan Joseph Note Added: 0128943
2021-02-16 09:37 Sven Barth Note Added: 0128948
2021-02-16 23:07 Ryan Joseph Note Added: 0128959
2021-02-16 23:08 Ryan Joseph Note Edited: 0128959 View Revisions
2021-02-16 23:09 Ryan Joseph Note Edited: 0128959 View Revisions
2021-02-16 23:53 Ryan Joseph Note Added: 0128960
2021-02-16 23:53 Ryan Joseph File Added: patch-5.diff
2021-02-16 23:56 Ryan Joseph Note Edited: 0128960 View Revisions
2021-02-17 09:49 Sven Barth Note Edited: 0128959 View Revisions
2021-02-17 09:50 Sven Barth Note Edited: 0128959 View Revisions
2021-02-21 18:58 Ryan Joseph Note Added: 0129067
2021-02-21 18:58 Ryan Joseph File Added: patch-6.diff
2021-02-22 09:50 Sven Barth Note Added: 0129084
2021-03-04 15:58 Ryan Joseph Note Added: 0129381
2021-03-04 21:52 Sven Barth Note Added: 0129386
2021-03-06 17:42 Sven Barth Note Added: 0129442
2021-03-06 18:35 Ryan Joseph Note Added: 0129449
2021-03-06 19:18 Ryan Joseph Note Edited: 0129449 View Revisions