View Issue Details

IDProjectCategoryView StatusLast Update
0035261FPCCompilerpublic2021-04-19 17:32
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...

Ryan Joseph

2021-03-15 17:32

reporter   ~0129695

Last edited: 2021-03-15 17:34

View 2 revisions

I made most of the changes you requested and added nested array inline specializations and other inline specializations like TRec<T> = TRect<integer> (just realized I didn't make them recursive yet to handle inline specializations!). I still maintain those other 2 issues (in my previous note) can not be resolved like you asked so please look that over for me so I can proceed one way or another. When we get that figured out I'll make all the tests and find more bugs. :)

Notes:

  - added tstringdef.getchardef to get the default compiler type for the string element, which is a useful utility function
  - added tabstractprocdef.is_generic_param which tells you if a tdef is a generic param which belongs to that procdef
  - non-generic param types are now accepted (as requested) and the call will fail during type checking but I don't understand why this is preferred
    since it means traveling a code path which is guaranteed to fail later.

Example of what I said before:
generic procedure DoThis<T>(param: specialize TRec<T>);
begin
end;
var
  r: specialize TRec<integer>;
begin
  DoThis(r); // specialize DoThis<integer> because TRec<integer> is compatible with TRec<T> 


Inline arrays specializations are for multi-dimensional arrays:

generic procedure DoThis<T>(param: specialize TArray<specialize TArray<T>>);

DoThis([[1],[2],[3]]);


patch-7.diff (42,454 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..bc01025d86 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,14 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          { try to specialize the procsym }
+          if srsym.could_be_implicitly_specialized and
+            try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+            foundanything:=true;
           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 +2469,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 +2791,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 +3239,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 +3608,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..79f8696963 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,581 @@ 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;
+
+      { returns true if the def a literal array, i.e. not a shortstring}
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := (def.typ=arraydef) 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,' = ',paramtype.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;
+          paradef:tdef;
+          i:integer;
+        begin
+          result:=tfplist.create;
+          pt:=tcallparanode(para);
+          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);
+              result.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+            end;
+          writeln('caller params:');
+          for i:=0 to result.count-1 do
+            writeln('  ',i,'=',ttypesym(result[i]).realname);
+        end;
+
+      { searches for the generic param in specializations }
+      function find_param_in_specialization(owner:tprocdef;genericparam:ttypesym; def:tstoreddef): boolean;
+        var
+          parasym: ttypesym;
+          k, i: integer;
+        begin
+          result:=false;
+          for i:=0 to def.genericparas.count-1 do
+            begin
+              parasym:=ttypesym(def.genericparas[i]);
+              if parasym.typedef.typesym=nil then
+                internalerror(2021020907);
+              { recurse into inline specialization }
+              if tstoreddef(parasym.typedef).is_specialization then
+                begin
+                  result:=find_param_in_specialization(owner,genericparam,tstoreddef(parasym.typedef));
+                  if result then
+                    exit;
+                end
+              else if (genericparam=parasym.typedef.typesym) and owner.is_generic_param(parasym.typedef) then
+                exit(true);
+            end;
+        end;
+
+      { searches for the generic param in arrays }
+      function find_param_in_array(owner:tprocdef;genericparam:ttypesym; def:tarraydef): boolean;
+        var
+          elementdef:tstoreddef;
+        begin
+          elementdef:=tstoreddef(def.elementdef);
+          if elementdef.typesym=nil then
+            internalerror(2021020906);
+          if is_array_literal(elementdef) then
+            result:=find_param_in_array(owner,genericparam,tarraydef(elementdef))
+          else
+            result:=(genericparam=elementdef.typesym) and owner.is_generic_param(elementdef);
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(owner:tprocdef;genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          i: integer;
+        begin
+          result:=false;
+          for i:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[i]);
+              
+              { handle array types by using element types (for example: array of T) }
+              if paravar.vardef.typ=arraydef then
+                result:=find_param_in_array(owner,genericparam,tarraydef(paravar.vardef))
+              { for specializations check search in generic params }
+              else if tstoreddef(paravar.vardef).is_specialization then
+                result:=find_param_in_specialization(owner,genericparam,tstoreddef(paravar.vardef))
+              { this can happen if the const vs type generic params are mismatched
+                within inline specializations }
+              else if paravar.vardef.typ=errordef then
+                exit(false)
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam=paravar.vardef.typesym) and owner.is_generic_param(paravar.vardef)
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+      
+      { handle generic specializations by using generic params from caller
+        to specialize the target. for example "TRec<Integer>" can use "Integer"
+        to specialize "TRec<T>" with "Integer" for "T". }
+      procedure handle_specializations(genericparams:tfphashlist; target_def,caller_def:tstoreddef);
+        var
+          i,
+          index: integer;
+          key: string;
+          target_param,
+          caller_param: ttypesym;
+        begin
+          { the target and the caller must the same generic def 
+            with the same set of generic parameters }
+          if target_def.genericdef<>caller_def.genericdef then
+            internalerror(2021020909);
+
+          for i:=0 to target_def.genericparas.count-1 do
+            begin
+              target_param:=ttypesym(target_def.genericparas[i]);
+              caller_param:=ttypesym(caller_def.genericparas[i]);
+
+              { reject generics with constants }
+              if (target_param.typ=constsym) or (caller_param.typ=constsym) then
+                exit;
+
+              key:=generic_param_hash(target_param.typedef);
+              writeln(i, ': ',key, ' = ', caller_param.realname);
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(key);
+              if index>=0 then
+                continue;
+
+              { add the type to the generic params }
+              genericparams.add(key,caller_param);
+            end;
+        end;
+
+      { specialize arrays by using element types but arrays may be multi-dimensional 
+        so we need to examine the caller/target pairs recursively in order to
+        verify the dimensionality is equal }
+      function handle_arrays(owner:tprocdef; target_def,caller_def:tarraydef; out target_element,caller_element:tdef): boolean;
+        begin
+          { the target and the caller are both arrays and the target is a 
+            specialization so we can recurse into the targets element def }
+          if is_array_literal(target_def.elementdef) and 
+            is_array_literal(caller_def.elementdef) and 
+            target_def.is_specialization then
+            result:=handle_arrays(owner,tarraydef(target_def.elementdef),tarraydef(caller_def.elementdef),target_element,caller_element)
+          else
+            begin
+              { the caller is an array which means the dimensionality is unbalanced
+                and thus the arrays are compatible }
+              if is_array_literal(caller_def.elementdef) then
+                exit(false);
+              { if the element is a generic param then return this type
+                along with the caller element type at the same level }
+              result:=owner.is_generic_param(target_def.elementdef);
+              if result then
+                begin
+                  target_element:=target_def.elementdef;
+                  caller_element:=caller_def.elementdef;
+                end;
+            end;
+        end;
+
+      { handle procvars by using the parameters from the caller to specialize
+        the parameters of the target generic procedure specialization. for example:
+
+          type generic TProc<S> = procedure(value: S);
+          generic procedure Run<T>(proc: specialize TProc<T>);
+          procedure DoCallback(value: integer);
+          Run(@DoCallback);
+
+        will specialize as Run<integer> because the signature
+        of DoCallback() matches TProc<S> so we can specialize "S"
+        with "integer", as they are both parameter #1
+      }
+
+      function handle_procvars(genericparams:tfphashlist; callerparams:tfplist; 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;
+          newparams: tfphashlist;
+          key: string;
+          index: integer;
+          valid_params: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match exactly }
+          // TODO: consider default values?
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { reject generics with constants }
+          for i:=0 to target_proc.genericdef.genericparas.count-1 do
+            if tsym(target_proc.genericdef.genericparas[i]).typ=constsym then
+              exit;
+
+          newparams:=tfphashlist.create;
+          valid_params:=0;
+
+          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
+                begin
+                  newparams.free;
+                  exit(false);
+                end;
+
+              if sp_generic_para in target_proc_para.vardef.typesym.symoptions 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 must not already be used }
+                  index:=genericparams.findindexof(key);
+                  if index=-1 then
+                    begin
+                      { add the type to the list }
+                      index:=newparams.findindexof(key);
+                      if index=-1 then
+                        newparams.add(key,caller_def.typesym);
+                    end;
+                end;
+
+              inc(valid_params);
+            end;
+
+          { if the count of valid params matches the target then
+            transfer the temporary params to the actual params }
+          result:=valid_params=target_proc.paras.count;
+          if result then
+            for i := 0 to newparams.count-1 do
+              genericparams.add(newparams.nameofindex(i),newparams[i]);
+
+          newparams.free;
+        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;
+          target_element,
+          caller_element: 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);
+
+              { const non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          writeln('► required: ',required_param_count, ' supplied: ', callerparams.count);
+
+          { 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(genericdef,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
+                genericdef.is_generic_param(tarraydef(target_def).elementdef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                  caller_def:=tstringdef(caller_def).getchardef;
+                end
+              { handle generic arrays }
+              else if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                handle_arrays(genericdef,tarraydef(target_def),tarraydef(caller_def),target_element,caller_element) then
+                begin
+                  target_def:=target_element;
+                  caller_def:=caller_element;
+                  target_key:=generic_param_hash(target_def);
+                end
+              { handle generic procvars }
+              else if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                tprocvardef(target_def).is_specialization and
+                handle_procvars(genericparams,callerparams,target_def,caller_def) then
+                begin
+                  continue;
+                end
+              { handle specialized objects by taking the base class as the type to specialize }    
+              else if is_class_or_object(caller_def) and 
+                is_class_or_object(target_def) and
+                genericdef.is_generic_param(target_def) then
+                begin
+                  target_key:=generic_param_hash(target_def);
+                  target_def:=tobjectdef(target_def).childof;
+                end
+              { handle generic specializations }
+              else if tstoreddef(caller_def).is_specialization and 
+                tstoreddef(target_def).is_specialization and
+                (tstoreddef(caller_def).genericdef=tstoreddef(target_def).genericdef) then
+                begin
+                  handle_specializations(genericparams,tstoreddef(target_def),tstoreddef(caller_def));
+                  continue;
+                end
+              { handle all other generic params }
+              else if target_def.typ=undefineddef then
+                target_key:=generic_param_hash(target_def);
+
+              if target_key<>'' then
+                writeln('  ',i,': ',target_key,' -> ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn))
+              else
+                writeln('  ',i,': ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn));
+
+              { 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>=0 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;
+        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));
+                if is_possible_specialization(callerparams,pd,genericparams) 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/symdef.pas b/compiler/symdef.pas
index da190640ad..017df3eb00 100644
--- a/compiler/symdef.pas
+++ b/compiler/symdef.pas
@@ -721,6 +721,8 @@ interface
           procedure declared_far;virtual;
           procedure declared_near;virtual;
           function generate_safecall_wrapper: boolean; virtual;
+          { returns true if the def is a generic param of the procdef }
+          function is_generic_param(def:tdef): boolean;
        private
           procedure count_para(p:TObject;arg:pointer);
           procedure insert_para(p:TObject;arg:pointer);
@@ -1003,6 +1005,8 @@ interface
           function alignment : shortint;override;
           function  needs_inittable : boolean;override;
           function  getvardef:longint;override;
+          { returns the default char type def for the string }
+          function getchardef: tdef;
        end;
        tstringdefclass = class of tstringdef;
 
@@ -2742,6 +2746,18 @@ implementation
         result:=vardef[stringtype];
       end;
 
+    function tstringdef.getchardef: tdef;
+      begin
+        case stringtype of
+          st_shortstring,
+          st_longstring,
+          st_ansistring:
+            result:=cansichartype;
+          st_unicodestring,
+          st_widestring:
+            result:=cwidechartype;   
+        end;
+      end;
 
     function tstringdef.alignment : shortint;
       begin
@@ -5975,6 +5991,17 @@ implementation
       end;
 
 
+    function tabstractprocdef.is_generic_param(def:tdef): boolean;
+      begin
+        if def.typ=undefineddef then
+          result:=def.owner=self.parast
+        else if def.typ=objectdef then
+          result:=(def.owner=self.parast) and (tstoreddef(def).genconstraintdata<>nil)
+        else
+          result:=false;
+      end;
+
+
 {***************************************************************************
                                   TPROCDEF
 ***************************************************************************}
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-7.diff (42,454 bytes)   

Ryan Joseph

2021-03-25 16:32

reporter   ~0129896

I haven't heard back from you in a couple weeks. Any word on this? I'm still stuck on what you want me to do about the "type symbol for anonymous/constant" function. I'm leaning towards needing a global type symbol for constant/literal types so we can alway call a function with "tstringdef" for example and get the default type symbol which is a string.

Sven Barth

2021-03-26 15:32

manager   ~0129910

I did not have the time to look at it. Though I do have an idea regarding the defs without a type symbol, but I first need to check whether that's feasible.

Ryan Joseph

2021-03-26 18:18

reporter   ~0129912

Thanks Sven. I know you're busy I just don't want to see this buried again for months since we're finally getting so close.

Sven Barth

2021-04-02 21:46

manager   ~0130051

Last edited: 2021-04-03 11:43

View 2 revisions

Following remarks:

- I agree with the compare_by_old_sortout_check now (though the result of that should really be changed to Boolean, but that is not your fault ;) )

- I'm still saying that the generic-vs-non-generic check should be last

Take the following example:

program timplicitspez;

{$mode objfpc}{$H+}
{$modeswitch IMPLICITFUNCTIONSPECIALIZATION}

function Test(const aStr: String): LongInt;
begin
  Result := 1;
end;

generic function Test<T>(aT: T): LongInt;
begin
  Result := 2;
end;

operator := (aArg: LongInt): String;
begin
  { potentially expensive conversion }
  Result := '';
end;

begin
  Writeln(Test('Hello World'));
  Writeln(Test(42));
end.


Right now your code will pick the existing String overload even if specializing the generic might be the better choice. If the user really wants the String one (or if the specializing the generic does not work) the user can still force the String overload by casting the parameter to a String.

- you may create the type symbols for defs that don't have one but you must not modify the def (setting the typesym) for the same reason as I didn't want one of your original changes: don't modify defs that might come from a different unit (you can see this when you use an untyped string constant from a different unit); instead you need to make sure that the type symbol is put into the parasymtable of the function that is picked at the end so that it is correctly handled regarding memory and such (this means that the owner of the symbols must also not be the owner of the def, but instead the procdef that is picked at the end); this means that you probably need to communicate more information from try_implicit_specialization to htypechk (namely a list of generated type symbols for each procdef that might either be freed or used at the end)

- you must not use SHORTSTRING for any string like type; this is completely wrong for WideChar based types and if $H+ is given it might be longer than 255 characters

Ryan Joseph

2021-04-03 02:57

reporter   ~0130054

- I was instructed and have in my original notes that the non-generic must AWLAYS win. I think that was for Delphi compatibility so maybe we need to check first? Here's the old thread: https://lists.freepascal.org/pipermail/fpc-pascal/2018-December/055225.html. If that's not the case then I don't think I need to do any of the stuff in compare_by_old_sortout_check or is_better_candidate because the normal overloading should do this already. I also don't understand how the compiler could know Test<T> is a "better" choice anyways.

- For the string types I'll make a case with the st_XXX constants (like st_shortstring, st_ansistring etc...) and use "search_system_type" to get the correct type from the system unit. I may not get the types rights so feel free to give me your list of preferred string types from System.pas.

- Not sure why I set the def typesym in create_unamed_typesym but I assume I needed it. I'll re-examine this area.

Sven Barth

2021-04-03 11:46

manager   ~0130058

I wasn't clear enough in that thread back then. What I mean is: the non-generic needs to take precedence if nothing else leads to a selection of a better one. And for this the selection between generic and non-generic needs to be at the end.

Ryan Joseph

2021-04-03 17:08

reporter   ~0130064

I'm not sure I understand then what you mean. In your example above the Test<T> should take precedence? I thought we were trying to prevent implicit specializations in ambiguous situations, which makes sense to me. If we're not doing that, then when exactly should compiler reject a generic and use the non-generic? I don't know what criteria you are thinking of.

Ryan Joseph

2021-04-06 18:03

reporter   ~0130140

Sven, I'm gonna ask this question on the dev list also to get some more insights and to hopefully speed up the process. I also just noticed that because I'm creating new type syms for string literals this is confusing the overloading when $H+ is on because the string types may not be the same. This may be part of the problem you're having but I still don't think I understand what you want.

Ryan Joseph

2021-04-08 19:31

reporter   ~0130181

here's the latest patch with the applied changes and unit tests to play with. If this is sound I'll work on removing comments/cruft last. I added some utility methods you may not approve of but the big ticket items are all FINALLY taken care of.

Now that I think of it there are some TODO comments where memory is leaking now but I don't want to assume what kinds of solutions you may have to this so let me know if its a problem or not.
patch-8.diff (43,889 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..4b5e9c4fcb 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,14 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          { try to specialize the procsym }
+          if srsym.could_be_implicitly_specialized and
+            try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+            foundanything:=true;
           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 +2469,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 +2791,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;
@@ -3330,6 +3311,23 @@ implementation
                                      res:=-1
                                    else
                                     res:=0;
+                                   { 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);
+                                       //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.');
+                                           res:=-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.');
+                                           res:=1;
+                                         end;
+                                     end;
                                  end;
                               end;
                            end;
@@ -3610,6 +3608,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/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..3a0a1d1762 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,618 @@ 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;
+
+      { returns true if the def a literal array such as [1,2,3] and not a shortstring}
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := (def.typ=arraydef) 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,' = ',paramtype.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(def:tdef): tsym;
+        var
+          newtype: tsym;
+        begin
+          newtype:=nil;
+          if is_stringlike(def) then
+            begin
+              if (def.typ=arraydef) and (ado_isconststring in tarraydef(def).arrayoptions) then
+                newtype:=nil
+              else
+                case tstringdef(def).stringtype of
+                  st_shortstring:
+                    newtype:=nil;
+                  st_longstring,
+                  st_ansistring:
+                    newtype:=search_system_type('ANSISTRING');
+                  st_widestring:
+                    newtype:=search_system_type('WIDESTRING');
+                  st_unicodestring:
+                    newtype:=search_system_type('UNICODESTRING');
+                end;
+              { not better string type was found so chose the default string type }
+              if newtype=nil then
+                begin
+                  if (cs_refcountedstrings in current_settings.localswitches) then
+                    begin
+                      if m_default_unicodestring in current_settings.modeswitches then
+                        newtype:=search_system_type('UNICODESTRING')
+                      else
+                        newtype:=search_system_type('ANSISTRING');
+                    end
+                  else
+                    newtype:=search_system_type('SHORTSTRING');
+                end;
+            end
+          else
+            begin
+              newtype:=ctypesym.create(def.typename,def);
+              newtype.owner:=def.owner;
+            end;
+          if newtype=nil then
+            internalerror(2021020904);
+          result:=newtype;
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(dummysym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paradef:tdef;
+          sym:tsym;
+          i:integer;
+        begin
+          result:=tfplist.create;
+          pt:=tcallparanode(para);
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { unnamed parameter types can not be specialized }
+              if paradef.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(paradef);
+                  // TODO: sym is leaking memory!
+                  result.insert(0,sym);
+                end
+              else
+                result.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+            end;
+          writeln('caller params:');
+          for i:=0 to result.count-1 do
+            writeln('  ',i,'=',ttypesym(result[i]).realname);
+        end;
+
+      { searches for the generic param in specializations }
+      function find_param_in_specialization(owner:tprocdef;genericparam:ttypesym; def:tstoreddef): boolean;
+        var
+          parasym: ttypesym;
+          k, i: integer;
+        begin
+          result:=false;
+          for i:=0 to def.genericparas.count-1 do
+            begin
+              parasym:=ttypesym(def.genericparas[i]);
+              { the generic param must have a named typesym }
+              if parasym.typedef.typesym=nil then
+                internalerror(2021020907);
+              { recurse into inline specialization }
+              if tstoreddef(parasym.typedef).is_specialization then
+                begin
+                  result:=find_param_in_specialization(owner,genericparam,tstoreddef(parasym.typedef));
+                  if result then
+                    exit;
+                end
+              else if (genericparam=parasym.typedef.typesym) and owner.is_generic_param(parasym.typedef) then
+                exit(true);
+            end;
+        end;
+
+      { searches for the generic param in arrays }
+      function find_param_in_array(owner:tprocdef;genericparam:ttypesym; def:tarraydef): boolean;
+        var
+          elementdef:tstoreddef;
+        begin
+          elementdef:=tstoreddef(def.elementdef);
+          { recurse into multi-dimensional array }
+          if elementdef.typ=arraydef then
+            result:=find_param_in_array(owner,genericparam,tarraydef(elementdef))
+          { something went wrong during parsing and the element is invalid }
+          else if elementdef.typ=errordef then
+            result:=false
+          else
+            begin
+              { the element must have a named typesym }
+              if elementdef.typesym=nil then
+                internalerror(2021020906);
+              result:=(genericparam=elementdef.typesym) and owner.is_generic_param(elementdef);
+            end;
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(owner:tprocdef;genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          i: integer;
+        begin
+          result:=false;
+          for i:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[i]);
+              
+              { handle array types by using element types (for example: array of T) }
+              if paravar.vardef.typ=arraydef then
+                result:=find_param_in_array(owner,genericparam,tarraydef(paravar.vardef))
+              { for specializations check search in generic params }
+              else if tstoreddef(paravar.vardef).is_specialization then
+                result:=find_param_in_specialization(owner,genericparam,tstoreddef(paravar.vardef))
+              { something went wrong during parsing and the parameter is invalid }
+              else if paravar.vardef.typ=errordef then
+                exit(false)
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam=paravar.vardef.typesym) and owner.is_generic_param(paravar.vardef)
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+      
+      { handle generic specializations by using generic params from caller
+        to specialize the target. for example "TRec<Integer>" can use "Integer"
+        to specialize "TRec<T>" with "Integer" for "T". }
+      procedure handle_specializations(genericparams:tfphashlist; target_def,caller_def:tstoreddef);
+        var
+          i,
+          index: integer;
+          key: string;
+          target_param,
+          caller_param: ttypesym;
+        begin
+          { the target and the caller must the same generic def 
+            with the same set of generic parameters }
+          if target_def.genericdef<>caller_def.genericdef then
+            internalerror(2021020909);
+
+          for i:=0 to target_def.genericparas.count-1 do
+            begin
+              target_param:=ttypesym(target_def.genericparas[i]);
+              caller_param:=ttypesym(caller_def.genericparas[i]);
+
+              { reject generics with constants }
+              if (target_param.typ=constsym) or (caller_param.typ=constsym) then
+                exit;
+
+              key:=generic_param_hash(target_param.typedef);
+              writeln(i, ': ',key, ' = ', caller_param.realname);
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(key);
+              if index>=0 then
+                continue;
+
+              { add the type to the generic params }
+              genericparams.add(key,caller_param);
+            end;
+        end;
+
+      { specialize arrays by using element types but arrays may be multi-dimensional 
+        so we need to examine the caller/target pairs recursively in order to
+        verify the dimensionality is equal }
+      function handle_arrays(owner:tprocdef; target_def,caller_def:tarraydef; out target_element,caller_element:tdef): boolean;
+        begin
+          { the target and the caller are both arrays and the target is a 
+            specialization so we can recurse into the targets element def }
+          if is_array_literal(target_def.elementdef) and 
+            is_array_literal(caller_def.elementdef) and 
+            target_def.is_specialization then
+            result:=handle_arrays(owner,tarraydef(target_def.elementdef),tarraydef(caller_def.elementdef),target_element,caller_element)
+          else
+            begin
+              { the caller is an array which means the dimensionality is unbalanced
+                and thus the arrays are compatible }
+              if is_array_literal(caller_def.elementdef) then
+                exit(false);
+              { if the element is a generic param then return this type
+                along with the caller element type at the same level }
+              result:=owner.is_generic_param(target_def.elementdef);
+              if result then
+                begin
+                  target_element:=target_def.elementdef;
+                  caller_element:=caller_def.elementdef;
+                end;
+            end;
+        end;
+
+      { handle procvars by using the parameters from the caller to specialize
+        the parameters of the target generic procedure specialization. for example:
+
+          type generic TProc<S> = procedure(value: S);
+          generic procedure Run<T>(proc: specialize TProc<T>);
+          procedure DoCallback(value: integer);
+          Run(@DoCallback);
+
+        will specialize as Run<integer> because the signature
+        of DoCallback() matches TProc<S> so we can specialize "S"
+        with "integer", as they are both parameter #1
+      }
+
+      function handle_procvars(genericparams:tfphashlist; callerparams:tfplist; 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;
+          newparams: tfphashlist;
+          key: string;
+          index: integer;
+          valid_params: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match exactly }
+          // TODO: consider default values?
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { reject generics with constants }
+          for i:=0 to target_proc.genericdef.genericparas.count-1 do
+            if tsym(target_proc.genericdef.genericparas[i]).typ=constsym then
+              exit;
+
+          newparams:=tfphashlist.create;
+          valid_params:=0;
+
+          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
+                begin
+                  newparams.free;
+                  exit(false);
+                end;
+
+              if sp_generic_para in target_proc_para.vardef.typesym.symoptions 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 must not already be used }
+                  index:=genericparams.findindexof(key);
+                  if index=-1 then
+                    begin
+                      { add the type to the list }
+                      index:=newparams.findindexof(key);
+                      if index=-1 then
+                        newparams.add(key,caller_def.typesym);
+                    end;
+                end;
+
+              inc(valid_params);
+            end;
+
+          { if the count of valid params matches the target then
+            transfer the temporary params to the actual params }
+          result:=valid_params=target_proc.paras.count;
+          if result then
+            for i := 0 to newparams.count-1 do
+              genericparams.add(newparams.nameofindex(i),newparams[i]);
+
+          newparams.free;
+        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;
+          target_element,
+          caller_element: 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);
+
+              { const non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          writeln('► required: ',required_param_count, ' supplied: ', callerparams.count);
+
+          { 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(genericdef,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
+                genericdef.is_generic_param(tarraydef(target_def).elementdef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                  caller_def:=tstringdef(caller_def).getchardef;
+                end
+              { handle generic arrays }
+              else if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                handle_arrays(genericdef,tarraydef(target_def),tarraydef(caller_def),target_element,caller_element) then
+                begin
+                  target_def:=target_element;
+                  caller_def:=caller_element;
+                  target_key:=generic_param_hash(target_def);
+                end
+              { handle generic procvars }
+              else if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                tprocvardef(target_def).is_specialization and
+                handle_procvars(genericparams,callerparams,target_def,caller_def) then
+                begin
+                  continue;
+                end
+              { handle specialized objects by taking the base class as the type to specialize }    
+              else if is_class_or_object(caller_def) and 
+                is_class_or_object(target_def) and
+                genericdef.is_generic_param(target_def) then
+                begin
+                  target_key:=generic_param_hash(target_def);
+                  target_def:=tobjectdef(target_def).childof;
+                end
+              { handle generic specializations }
+              else if tstoreddef(caller_def).is_specialization and 
+                tstoreddef(target_def).is_specialization and
+                (tstoreddef(caller_def).genericdef=tstoreddef(target_def).genericdef) then
+                begin
+                  handle_specializations(genericparams,tstoreddef(target_def),tstoreddef(caller_def));
+                  continue;
+                end
+              { handle all other generic params }
+              else if target_def.typ=undefineddef then
+                target_key:=generic_param_hash(target_def);
+
+              if caller_def.typesym <> nil then
+                begin
+                  if target_key<>'' then
+                    writeln('  ',i,': ',target_key,' -> ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn))
+                  else
+                    writeln('  ',i,': ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn));
+                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>=0 then
+                continue;
+
+              { the caller type may not have a typesym so we need to create an unamed one }
+              if caller_def.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(caller_def);
+                  // TODO: sym is leaking memory!
+                  genericparams.add(target_key,sym);
+                end
+              else
+                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;
+        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));
+                if is_possible_specialization(callerparams,pd,genericparams) 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/symdef.pas b/compiler/symdef.pas
index da190640ad..017df3eb00 100644
--- a/compiler/symdef.pas
+++ b/compiler/symdef.pas
@@ -721,6 +721,8 @@ interface
           procedure declared_far;virtual;
           procedure declared_near;virtual;
           function generate_safecall_wrapper: boolean; virtual;
+          { returns true if the def is a generic param of the procdef }
+          function is_generic_param(def:tdef): boolean;
        private
           procedure count_para(p:TObject;arg:pointer);
           procedure insert_para(p:TObject;arg:pointer);
@@ -1003,6 +1005,8 @@ interface
           function alignment : shortint;override;
           function  needs_inittable : boolean;override;
           function  getvardef:longint;override;
+          { returns the default char type def for the string }
+          function getchardef: tdef;
        end;
        tstringdefclass = class of tstringdef;
 
@@ -2742,6 +2746,18 @@ implementation
         result:=vardef[stringtype];
       end;
 
+    function tstringdef.getchardef: tdef;
+      begin
+        case stringtype of
+          st_shortstring,
+          st_longstring,
+          st_ansistring:
+            result:=cansichartype;
+          st_unicodestring,
+          st_widestring:
+            result:=cwidechartype;   
+        end;
+      end;
 
     function tstringdef.alignment : shortint;
       begin
@@ -5975,6 +5991,17 @@ implementation
       end;
 
 
+    function tabstractprocdef.is_generic_param(def:tdef): boolean;
+      begin
+        if def.typ=undefineddef then
+          result:=def.owner=self.parast
+        else if def.typ=objectdef then
+          result:=(def.owner=self.parast) and (tstoreddef(def).genconstraintdata<>nil)
+        else
+          result:=false;
+      end;
+
+
 {***************************************************************************
                                   TPROCDEF
 ***************************************************************************}
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-8.diff (43,889 bytes)   
final.zip (25,149 bytes)

Ryan Joseph

2021-04-16 19:30

reporter   ~0130414

Here is the requested changes (and some utility methods) for create_unamed_typesym and "array of const rejection" + extra test for that.
patch-9.diff (47,466 bytes)   
diff --git a/compiler/defutil.pas b/compiler/defutil.pas
index 139ce98d17..61da0dd0ee 100644
--- a/compiler/defutil.pas
+++ b/compiler/defutil.pas
@@ -211,6 +211,9 @@ interface
     {# Returns true if p is a short string type }
     function is_shortstring(p : tdef) : boolean;
 
+    {# Returns true if p is a constant string type }
+    function is_constant_string(p : tdef) : boolean;
+
     {# Returns true if p is any pointer def }
     function is_pointer(p : tdef) : boolean;
 
@@ -819,7 +822,10 @@ implementation
     function is_array_of_const(p : tdef) : boolean;
       begin
          result:=(p.typ=arraydef) and
-                 (ado_IsArrayOfConst in tarraydef(p).arrayoptions);
+                 (ado_IsArrayOfConst in tarraydef(p).arrayoptions) and
+                 { consider it an array-of-const in the strict sense only if it
+                   isn't an array constructor }
+                 not (ado_IsConstructor in tarraydef(p).arrayoptions);
       end;
 
     { true, if p points to a special array, bitpacked arrays aren't special in this regard though }
@@ -901,6 +907,12 @@ implementation
                          (tstringdef(p).stringtype=st_shortstring);
       end;
 
+    { true if p is a constant string def }
+    function is_constant_string(p : tdef) : boolean;
+      begin
+        is_constant_string:=(p.typ=arraydef) and (ado_isconststring in tarraydef(p).arrayoptions);
+      end;
+    
 
     { true if p is bit packed array def }
     function is_packed_array(p: tdef) : boolean;
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..4b5e9c4fcb 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,14 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          { try to specialize the procsym }
+          if srsym.could_be_implicitly_specialized and
+            try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+            foundanything:=true;
           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 +2469,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 +2791,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;
@@ -3330,6 +3311,23 @@ implementation
                                      res:=-1
                                    else
                                     res:=0;
+                                   { 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);
+                                       //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.');
+                                           res:=-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.');
+                                           res:=1;
+                                         end;
+                                     end;
                                  end;
                               end;
                            end;
@@ -3610,6 +3608,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/nld.pas b/compiler/nld.pas
index c6eab85ecf..aa62c738cc 100644
--- a/compiler/nld.pas
+++ b/compiler/nld.pas
@@ -1113,6 +1113,7 @@ implementation
         hdef  : tdef;
         hp    : tarrayconstructornode;
         len   : longint;
+        diff,
         varia : boolean;
         eq    : tequaltype;
         hnodetype : tnodetype;
@@ -1136,6 +1137,7 @@ implementation
         hnodetype:=errorn;
         len:=0;
         varia:=false;
+        diff:=false;
         if assigned(left) then
          begin
            hp:=self;
@@ -1164,6 +1166,8 @@ implementation
                    end
                  else
                    eq:=compare_defs(hdef,hp.left.resultdef,hp.left.nodetype);
+                 if eq=te_incompatible then
+                   diff:=true;
                  if (not varia) and (eq<te_equal) then
                    begin
                      { If both are integers we need to take the type that can hold both
@@ -1194,6 +1198,8 @@ implementation
          include(tarraydef(resultdef).arrayoptions,ado_IsConstructor);
          if varia then
            include(tarraydef(resultdef).arrayoptions,ado_IsVariant);
+         if diff then
+           include(tarraydef(resultdef).arrayoptions,ado_IsArrayOfConst);
          tarraydef(resultdef).elementdef:=hdef;
       end;
 
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..f6d95ee14c 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,614 @@ 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;
+
+      { returns true if the def a literal array such as [1,2,3] and not a shortstring }
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := (def.typ=arraydef) and not is_constant_string(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,' = ',paramtype.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(def:tdef): tsym;
+        var
+          newtype: tsym;
+        begin
+          newtype:=nil;
+          if is_constant_string(def) then
+            begin
+              { for constant strings we need to respect various modeswitches }
+              if (cs_refcountedstrings in current_settings.localswitches) then
+                begin
+                  if m_default_unicodestring in current_settings.modeswitches then
+                    newtype:=search_system_type('UNICODESTRING')
+                  else
+                    newtype:=search_system_type('ANSISTRING');
+                end
+              else
+                newtype:=search_system_type('SHORTSTRING');
+            end
+          else if def.typ=stringdef then
+            newtype:=tstringdef(def).get_default_string_type
+          else
+            begin
+              newtype:=ctypesym.create(def.typename,def);
+              newtype.owner:=def.owner;
+            end;
+          if newtype=nil then
+            internalerror(2021020904);
+          result:=newtype;
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(dummysym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paradef:tdef;
+          sym:tsym;
+          i:integer;
+        begin
+          result:=tfplist.create;
+          pt:=tcallparanode(para);
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { unnamed parameter types can not be specialized }
+              if paradef.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(paradef);
+                  // TODO: sym is leaking memory!
+                  result.insert(0,sym);
+                end
+              else
+                result.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+            end;
+          writeln('caller params:');
+          for i:=0 to result.count-1 do
+            writeln('  ',i,'=',ttypesym(result[i]).realname);
+        end;
+
+      { searches for the generic param in specializations }
+      function find_param_in_specialization(owner:tprocdef;genericparam:ttypesym; def:tstoreddef): boolean;
+        var
+          parasym: ttypesym;
+          k, i: integer;
+        begin
+          result:=false;
+          for i:=0 to def.genericparas.count-1 do
+            begin
+              parasym:=ttypesym(def.genericparas[i]);
+              { the generic param must have a named typesym }
+              if parasym.typedef.typesym=nil then
+                internalerror(2021020907);
+              { recurse into inline specialization }
+              if tstoreddef(parasym.typedef).is_specialization then
+                begin
+                  result:=find_param_in_specialization(owner,genericparam,tstoreddef(parasym.typedef));
+                  if result then
+                    exit;
+                end
+              else if (genericparam=parasym.typedef.typesym) and owner.is_generic_param(parasym.typedef) then
+                exit(true);
+            end;
+        end;
+
+      { searches for the generic param in arrays }
+      function find_param_in_array(owner:tprocdef;genericparam:ttypesym; def:tarraydef): boolean;
+        var
+          elementdef:tstoreddef;
+        begin
+          elementdef:=tstoreddef(def.elementdef);
+          { recurse into multi-dimensional array }
+          if elementdef.typ=arraydef then
+            result:=find_param_in_array(owner,genericparam,tarraydef(elementdef))
+          { something went wrong during parsing and the element is invalid }
+          else if elementdef.typ=errordef then
+            result:=false
+          else
+            begin
+              { the element must have a named typesym }
+              if elementdef.typesym=nil then
+                internalerror(2021020906);
+              result:=(genericparam=elementdef.typesym) and owner.is_generic_param(elementdef);
+            end;
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(owner:tprocdef;genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          i: integer;
+        begin
+          result:=false;
+          for i:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[i]);
+              
+              { handle array types by using element types (for example: array of T) }
+              if paravar.vardef.typ=arraydef then
+                result:=find_param_in_array(owner,genericparam,tarraydef(paravar.vardef))
+              { for specializations check search in generic params }
+              else if tstoreddef(paravar.vardef).is_specialization then
+                result:=find_param_in_specialization(owner,genericparam,tstoreddef(paravar.vardef))
+              { something went wrong during parsing and the parameter is invalid }
+              else if paravar.vardef.typ=errordef then
+                exit(false)
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam=paravar.vardef.typesym) and owner.is_generic_param(paravar.vardef)
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+      
+      { handle generic specializations by using generic params from caller
+        to specialize the target. for example "TRec<Integer>" can use "Integer"
+        to specialize "TRec<T>" with "Integer" for "T". }
+      procedure handle_specializations(genericparams:tfphashlist; target_def,caller_def:tstoreddef);
+        var
+          i,
+          index: integer;
+          key: string;
+          target_param,
+          caller_param: ttypesym;
+        begin
+          { the target and the caller must the same generic def 
+            with the same set of generic parameters }
+          if target_def.genericdef<>caller_def.genericdef then
+            internalerror(2021020909);
+
+          for i:=0 to target_def.genericparas.count-1 do
+            begin
+              target_param:=ttypesym(target_def.genericparas[i]);
+              caller_param:=ttypesym(caller_def.genericparas[i]);
+
+              { reject generics with constants }
+              if (target_param.typ=constsym) or (caller_param.typ=constsym) then
+                exit;
+
+              key:=generic_param_hash(target_param.typedef);
+              writeln(i, ': ',key, ' = ', caller_param.realname);
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(key);
+              if index>=0 then
+                continue;
+
+              { add the type to the generic params }
+              genericparams.add(key,caller_param);
+            end;
+        end;
+
+      { specialize arrays by using element types but arrays may be multi-dimensional 
+        so we need to examine the caller/target pairs recursively in order to
+        verify the dimensionality is equal }
+      function handle_arrays(owner:tprocdef; target_def,caller_def:tarraydef; out target_element,caller_element:tdef): boolean;
+        begin
+          { the target and the caller are both arrays and the target is a 
+            specialization so we can recurse into the targets element def }
+          if is_array_literal(target_def.elementdef) and 
+            is_array_literal(caller_def.elementdef) and 
+            target_def.is_specialization then
+            result:=handle_arrays(owner,tarraydef(target_def.elementdef),tarraydef(caller_def.elementdef),target_element,caller_element)
+          else
+            begin
+              { the caller is an array which means the dimensionality is unbalanced
+                and thus the arrays are compatible }
+              if is_array_literal(caller_def.elementdef) then
+                exit(false);
+              { if the element is a generic param then return this type
+                along with the caller element type at the same level }
+              result:=owner.is_generic_param(target_def.elementdef);
+              if result then
+                begin
+                  target_element:=target_def.elementdef;
+                  caller_element:=caller_def.elementdef;
+                end;
+            end;
+        end;
+
+      { handle procvars by using the parameters from the caller to specialize
+        the parameters of the target generic procedure specialization. for example:
+
+          type generic TProc<S> = procedure(value: S);
+          generic procedure Run<T>(proc: specialize TProc<T>);
+          procedure DoCallback(value: integer);
+          Run(@DoCallback);
+
+        will specialize as Run<integer> because the signature
+        of DoCallback() matches TProc<S> so we can specialize "S"
+        with "integer", as they are both parameter #1
+      }
+
+      function handle_procvars(genericparams:tfphashlist; callerparams:tfplist; 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;
+          newparams: tfphashlist;
+          key: string;
+          index: integer;
+          valid_params: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match exactly }
+          // TODO: consider default values?
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { reject generics with constants }
+          for i:=0 to target_proc.genericdef.genericparas.count-1 do
+            if tsym(target_proc.genericdef.genericparas[i]).typ=constsym then
+              exit;
+
+          newparams:=tfphashlist.create;
+          valid_params:=0;
+
+          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
+                begin
+                  newparams.free;
+                  exit(false);
+                end;
+
+              if sp_generic_para in target_proc_para.vardef.typesym.symoptions 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 must not already be used }
+                  index:=genericparams.findindexof(key);
+                  if index=-1 then
+                    begin
+                      { add the type to the list }
+                      index:=newparams.findindexof(key);
+                      if index=-1 then
+                        newparams.add(key,caller_def.typesym);
+                    end;
+                end;
+
+              inc(valid_params);
+            end;
+
+          { if the count of valid params matches the target then
+            transfer the temporary params to the actual params }
+          result:=valid_params=target_proc.paras.count;
+          if result then
+            for i := 0 to newparams.count-1 do
+              genericparams.add(newparams.nameofindex(i),newparams[i]);
+
+          newparams.free;
+        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;
+          target_element,
+          caller_element: tdef;
+          required_param_count: integer;
+          adef: tarraydef;
+        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 target function parameters }
+          paras:=tfplist.create;
+          for i:=0 to genericdef.paras.count-1 do
+            begin
+              paravar:=tparavarsym(genericdef.paras[i]);
+              { ignore hidden parameters }
+              if vo_is_hidden_para in paravar.varoptions then
+                continue;
+              paras.add(paravar);
+
+              { const non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          writeln('► required: ',required_param_count, ' supplied: ', callerparams.count);
+
+          { 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(genericdef,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
+                genericdef.is_generic_param(tarraydef(target_def).elementdef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                  caller_def:=tstringdef(caller_def).get_default_char_def;
+                end
+              { non-uniform array constructors (i.e. array of const) are not compatible 
+                with normal arrays like "array of T" so we reject them }
+              else if is_array_literal(target_def) and
+                (caller_def.typ=arraydef) and 
+                (ado_IsConstructor in tarraydef(caller_def).arrayoptions) and
+                (ado_IsArrayOfConst in tarraydef(caller_def).arrayoptions) then
+                begin
+                  continue;
+                end
+              { handle generic arrays }
+              else if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                handle_arrays(genericdef,tarraydef(target_def),tarraydef(caller_def),target_element,caller_element) then
+                begin
+                  target_def:=target_element;
+                  caller_def:=caller_element;
+                  target_key:=generic_param_hash(target_def);
+                end
+              { handle generic procvars }
+              else if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                tprocvardef(target_def).is_specialization and
+                handle_procvars(genericparams,callerparams,target_def,caller_def) then
+                begin
+                  continue;
+                end
+              { handle specialized objects by taking the base class as the type to specialize }    
+              else if is_class_or_object(caller_def) and 
+                is_class_or_object(target_def) and
+                genericdef.is_generic_param(target_def) then
+                begin
+                  target_key:=generic_param_hash(target_def);
+                  target_def:=tobjectdef(target_def).childof;
+                end
+              { handle generic specializations }
+              else if tstoreddef(caller_def).is_specialization and 
+                tstoreddef(target_def).is_specialization and
+                (tstoreddef(caller_def).genericdef=tstoreddef(target_def).genericdef) then
+                begin
+                  handle_specializations(genericparams,tstoreddef(target_def),tstoreddef(caller_def));
+                  continue;
+                end
+              { handle all other generic params }
+              else if target_def.typ=undefineddef then
+                target_key:=generic_param_hash(target_def);
+
+              if caller_def.typesym <> nil then
+                begin
+                  if target_key<>'' then
+                    writeln('  ',i,': ',target_key,' -> ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn))
+                  else
+                    writeln('  ',i,': ',caller_def.typesym.RealName, ' ', compare_defs(caller_def,target_def,nothingn));
+                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>=0 then
+                continue;
+
+              { the caller type may not have a typesym so we need to create an unamed one }
+              if caller_def.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(caller_def);
+                  // TODO: sym is leaking memory!
+                  genericparams.add(target_key,sym);
+                end
+              else
+                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;
+        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));
+                if is_possible_specialization(callerparams,pd,genericparams) 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/symdef.pas b/compiler/symdef.pas
index da190640ad..31d41cb1b7 100644
--- a/compiler/symdef.pas
+++ b/compiler/symdef.pas
@@ -721,6 +721,8 @@ interface
           procedure declared_far;virtual;
           procedure declared_near;virtual;
           function generate_safecall_wrapper: boolean; virtual;
+          { returns true if the def is a generic param of the procdef }
+          function is_generic_param(def:tdef): boolean;
        private
           procedure count_para(p:TObject;arg:pointer);
           procedure insert_para(p:TObject;arg:pointer);
@@ -1003,6 +1005,10 @@ interface
           function alignment : shortint;override;
           function  needs_inittable : boolean;override;
           function  getvardef:longint;override;
+          { returns the default char type def for the string }
+          function get_default_char_def: tdef;
+          { returns the default string type }
+          function get_default_string_type: tsym;
        end;
        tstringdefclass = class of tstringdef;
 
@@ -2743,6 +2749,39 @@ implementation
       end;
 
 
+    function tstringdef.get_default_char_def: tdef;
+      begin
+        case stringtype of
+          st_shortstring,
+          st_longstring,
+          st_ansistring:
+            result:=cansichartype;
+          st_unicodestring,
+          st_widestring:
+            result:=cwidechartype;   
+        end;
+      end;
+
+
+    function tstringdef.get_default_string_type: tsym;
+      begin
+        case stringtype of
+          st_shortstring:
+            result:=search_system_type('SHORTSTRING');
+          { st_longstring is currently not supported but 
+            when it is this case will need to be supplied }
+          st_longstring:
+            internalerror(2021040801);
+          st_ansistring:
+            result:=search_system_type('ANSISTRING');
+          st_widestring:
+            result:=search_system_type('WIDESTRING');
+          st_unicodestring:
+            result:=search_system_type('UNICODESTRING');
+        end
+      end;
+
+
     function tstringdef.alignment : shortint;
       begin
         case stringtype of
@@ -5975,6 +6014,17 @@ implementation
       end;
 
 
+    function tabstractprocdef.is_generic_param(def:tdef): boolean;
+      begin
+        if def.typ=undefineddef then
+          result:=def.owner=self.parast
+        else if def.typ=objectdef then
+          result:=(def.owner=self.parast) and (tstoreddef(def).genconstraintdata<>nil)
+        else
+          result:=false;
+      end;
+
+
 {***************************************************************************
                                   TPROCDEF
 ***************************************************************************}
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-9.diff (47,466 bytes)   
timpfuncspez33.pp (286 bytes)   
{%FAIL}
{$mode objfpc}
{$modeswitch implicitfunctionspecialization}
{
  Non-uniform array constructors are not compatible with "array of T"
}

program timpfuncspez33;

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

begin
  DoThis([
    1,
    'a',
    TObject.Create
  ]);
end.
timpfuncspez33.pp (286 bytes)   

Sven Barth

2021-04-17 12:57

manager   ~0130424

Remarks:

- is_constant_string is not required, because there already exists is_conststring_array which does the same (hadn't seen that earlier either)
- the move of the comment in is_visible_for_object is unnecessary
- now that we're nearing finalization you need to adjust ppudump as well and need to make sure that "make all" on the top level works
- obviously you need to clear out all your debug output ;)
- some changes have superfluous spaces at the end (e.g. in tstringdef.get_default_char_def after cwidechartype, so best enable Lazarus' display of space characters)
- regarding the tests:
  - maybe also add a few tests for Delphi mode?
  - maybe also add some more tests for overloading (can also be a bigger one), e.g. with different amounts of generic parameters, same parameters with different number of parameters and non-generic overlodas
  - tests that are only required to compile should have a {%NORUN} at the top (like timpfuncspez21)
  - timpfuncspez5, timpfuncspez6: you should check whether the type parameter is indeed the enum instead of printing it
  - timpfuncspez9: maybe also do a test inside a class/record/object (so that a method calls a generic method), maybe also something with inherited
  - timpfuncspez21: maybe also test with a subclass of TMyClass?
  - program name of test timpfuncspez25.pp doesn't match
  - side note: shouldn't timpfuncspez25 also fail without any implicit specializations involved?

Ryan Joseph

2021-04-17 17:13

reporter   ~0130430

Last edited: 2021-04-17 17:46

View 2 revisions

the move of the comment in is_visible_for_object is unnecessary"
What do you mean "the move" of the comment? The comment shouldn't be there or the if statement I added doesn't need to be there.

now that we're nearing finalization you need to adjust ppudump as well and need to make sure that "make all" on the top level works
You mean the overloaded symbols in tprocsym.genprocsymovlds need to be written to ppudump or the modeswitch? I'm not very familiar with ppudump.

shouldn't timpfuncspez25 also fail without any implicit specializations involved?
You're right. I'll remove it or modify it.

- Where is is_conststring_array? I don't see it anywhere?

Sven Barth

2021-04-18 13:49

manager   ~0130440

> What do you mean "the move" of the comment? The comment shouldn't be there or the if statement I added doesn't need to be there.

I mean this part in the changes for 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]);


> You mean the overloaded symbols in tprocsym.genprocsymovlds need to be written to ppudump or the modeswitch? I'm not very familiar with ppudump.

No, that is already handled. Just do a make all at the top level and you'll see what I mean.

> You're right. I'll remove it or modify it.

You don't need to remove or modify it, it's more that we need an additional tgenconstX test for this.

> Where is is_conststring_array? I don't see it anywhere?

defutil.pas, line 153. It's a recent addition by Jonas (~ 7 weeks ago).

Another thing I noticed: in tstringdef.get_default_string_type you don't need to use search_system_type, because all of these are already preloaded by the compiler as cshortstringtype, cansistringtype, cwidestringtype and cunicodestringtype respectively (they are defs, so you need the typesym field or you need to change the method to return the def and then use typesym inside your create_unnamed_typesym (which might be more consistent with get_default_char_def). Also inside your create_unnamed_typesym you should use the c*stringtype instead of serach_system_type as well.

Ryan Joseph

2021-04-18 18:55

reporter   ~0130442

I've made those changes (I think) and added a couple other tests but honestly I'm not certain what else you want to see with inherited/overloading so feel free to suggest something or include your own. I'll probably think of more later but that's all I can think of for now.
final-2.zip (29,039 bytes)
patch-10.diff (45,502 bytes)   
diff --git a/compiler/defutil.pas b/compiler/defutil.pas
index 852d2cfa5a..b9c50dfa87 100644
--- a/compiler/defutil.pas
+++ b/compiler/defutil.pas
@@ -821,7 +821,10 @@ implementation
     function is_array_of_const(p : tdef) : boolean;
       begin
          result:=(p.typ=arraydef) and
-                 (ado_IsArrayOfConst in tarraydef(p).arrayoptions);
+                 (ado_IsArrayOfConst in tarraydef(p).arrayoptions) and
+                 { consider it an array-of-const in the strict sense only if it
+                   isn't an array constructor }
+                 not (ado_IsConstructor in tarraydef(p).arrayoptions);
       end;
 
     function is_conststring_array(p: tdef): boolean;
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 0d0e6f4aba..9cb4b62902 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);
@@ -2245,10 +2244,14 @@ implementation
           { add all definitions }
           result:=false;
           foundanything:=false;
+          { try to specialize the procsym }
+          if srsym.could_be_implicitly_specialized and
+            try_implicit_specialization(srsym,FParaNode,ProcdefOverloadList,tsym(FProcsym),result) then
+            foundanything:=true;
           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
@@ -2467,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
@@ -2784,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;
@@ -3331,6 +3312,15 @@ implementation
                                      res:=-1
                                    else
                                     res:=0;
+                                   { 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
+                                       if (currpd^.data.is_specialization and not bestpd^.data.is_specialization) then
+                                         res:=-1
+                                       else if (not currpd^.data.is_specialization and bestpd^.data.is_specialization) then
+                                         res:=1;
+                                     end;
                                  end;
                               end;
                            end;
@@ -3611,6 +3601,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/nld.pas b/compiler/nld.pas
index 7be26db2bc..cba357ad11 100644
--- a/compiler/nld.pas
+++ b/compiler/nld.pas
@@ -1113,6 +1113,7 @@ implementation
         hdef  : tdef;
         hp    : tarrayconstructornode;
         len   : longint;
+        diff,
         varia : boolean;
         eq    : tequaltype;
         hnodetype : tnodetype;
@@ -1136,6 +1137,7 @@ implementation
         hnodetype:=errorn;
         len:=0;
         varia:=false;
+        diff:=false;
         if assigned(left) then
          begin
            hp:=self;
@@ -1164,6 +1166,10 @@ implementation
                    end
                  else
                    eq:=compare_defs(hdef,hp.left.resultdef,hp.left.nodetype);
+                 { the element is not compatible with the previous element
+                   which means the constructor is array of const }
+                 if eq=te_incompatible then
+                   diff:=true;
                  if (not varia) and (eq<te_equal) then
                    begin
                      { If both are integers we need to take the type that can hold both
@@ -1194,6 +1200,8 @@ implementation
          include(tarraydef(resultdef).arrayoptions,ado_IsConstructor);
          if varia then
            include(tarraydef(resultdef).arrayoptions,ado_IsVariant);
+         if diff then
+           include(tarraydef(resultdef).arrayoptions,ado_IsArrayOfConst);
          tarraydef(resultdef).elementdef:=hdef;
       end;
 
diff --git a/compiler/pgenutil.pas b/compiler/pgenutil.pas
index 4804995dbb..6b5df29471 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,590 @@ 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;
+
+      { returns true if the def a literal array such as [1,2,3] and not a shortstring }
+      function is_array_literal(def:tdef): boolean;
+        begin
+          result := (def.typ=arraydef) and not is_conststring_array(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);
+              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;
+        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(def:tdef): tsym;
+        var
+          newtype: tsym;
+        begin
+          newtype:=nil;
+          if is_conststring_array(def) then
+            begin
+              { for constant strings we need to respect various modeswitches }
+              if (cs_refcountedstrings in current_settings.localswitches) then
+                begin
+                  if m_default_unicodestring in current_settings.modeswitches then
+                    newtype:=cunicodestringtype.typesym
+                  else
+                    newtype:=cansistringtype.typesym;
+                end
+              else
+                newtype:=cshortstringtype.typesym;
+            end
+          else if def.typ=stringdef then
+            newtype:=tstringdef(def).get_default_string_type.typesym
+          else
+            begin
+              newtype:=ctypesym.create(def.typename,def);
+              newtype.owner:=def.owner;
+            end;
+          if newtype=nil then
+            internalerror(2021020904);
+          result:=newtype;
+        end;
+
+      { make an ordered list of parameters from the caller }
+      function make_param_list(dummysym:tsym;para:tnode): tfplist;
+        var
+          pt: tcallparanode;
+          paradef:tdef;
+          sym:tsym;
+          i:integer;
+        begin
+          result:=tfplist.create;
+          pt:=tcallparanode(para);
+          while assigned(pt) do
+            begin
+              paradef:=pt.paravalue.resultdef;
+              { unnamed parameter types can not be specialized }
+              if paradef.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(paradef);
+                  // TODO: sym is leaking memory!
+                  result.insert(0,sym);
+                end
+              else
+                result.insert(0,paradef.typesym);
+              pt:=tcallparanode(pt.nextpara);
+            end;
+        end;
+
+      { searches for the generic param in specializations }
+      function find_param_in_specialization(owner:tprocdef;genericparam:ttypesym; def:tstoreddef): boolean;
+        var
+          parasym: ttypesym;
+          k, i: integer;
+        begin
+          result:=false;
+          for i:=0 to def.genericparas.count-1 do
+            begin
+              parasym:=ttypesym(def.genericparas[i]);
+              { the generic param must have a named typesym }
+              if parasym.typedef.typesym=nil then
+                internalerror(2021020907);
+              { recurse into inline specialization }
+              if tstoreddef(parasym.typedef).is_specialization then
+                begin
+                  result:=find_param_in_specialization(owner,genericparam,tstoreddef(parasym.typedef));
+                  if result then
+                    exit;
+                end
+              else if (genericparam=parasym.typedef.typesym) and owner.is_generic_param(parasym.typedef) then
+                exit(true);
+            end;
+        end;
+
+      { searches for the generic param in arrays }
+      function find_param_in_array(owner:tprocdef;genericparam:ttypesym; def:tarraydef): boolean;
+        var
+          elementdef:tstoreddef;
+        begin
+          elementdef:=tstoreddef(def.elementdef);
+          { recurse into multi-dimensional array }
+          if elementdef.typ=arraydef then
+            result:=find_param_in_array(owner,genericparam,tarraydef(elementdef))
+          { something went wrong during parsing and the element is invalid }
+          else if elementdef.typ=errordef then
+            result:=false
+          else
+            begin
+              { the element must have a named typesym }
+              if elementdef.typesym=nil then
+                internalerror(2021020906);
+              result:=(genericparam=elementdef.typesym) and owner.is_generic_param(elementdef);
+            end;
+        end;
+
+      { tests if the generic param is used in the parameter list }
+      function is_generic_param_used(owner:tprocdef;genericparam:ttypesym;paras:tfplist): boolean;
+        var
+          paravar:tparavarsym;
+          i: integer;
+        begin
+          result:=false;
+          for i:=0 to paras.count-1 do
+            begin
+              paravar:=tparavarsym(paras[i]);
+
+              { handle array types by using element types (for example: array of T) }
+              if paravar.vardef.typ=arraydef then
+                result:=find_param_in_array(owner,genericparam,tarraydef(paravar.vardef))
+              { for specializations check search in generic params }
+              else if tstoreddef(paravar.vardef).is_specialization then
+                result:=find_param_in_specialization(owner,genericparam,tstoreddef(paravar.vardef))
+              { something went wrong during parsing and the parameter is invalid }
+              else if paravar.vardef.typ=errordef then
+                exit(false)
+              else
+                begin
+                  if paravar.vardef.typesym=nil then
+                    internalerror(2021020905);
+                  result:=(genericparam=paravar.vardef.typesym) and owner.is_generic_param(paravar.vardef)
+                end;
+
+              { exit if we find a used parameter }
+              if result then
+                exit;
+            end;
+        end;
+
+      { handle generic specializations by using generic params from caller
+        to specialize the target. for example "TRec<Integer>" can use "Integer"
+        to specialize "TRec<T>" with "Integer" for "T". }
+      procedure handle_specializations(genericparams:tfphashlist; target_def,caller_def:tstoreddef);
+        var
+          i,
+          index: integer;
+          key: string;
+          target_param,
+          caller_param: ttypesym;
+        begin
+          { the target and the caller must the same generic def 
+            with the same set of generic parameters }
+          if target_def.genericdef<>caller_def.genericdef then
+            internalerror(2021020909);
+
+          for i:=0 to target_def.genericparas.count-1 do
+            begin
+              target_param:=ttypesym(target_def.genericparas[i]);
+              caller_param:=ttypesym(caller_def.genericparas[i]);
+
+              { reject generics with constants }
+              if (target_param.typ=constsym) or (caller_param.typ=constsym) then
+                exit;
+
+              key:=generic_param_hash(target_param.typedef);
+
+              { the generic param is already used }
+              index:=genericparams.findindexof(key);
+              if index>=0 then
+                continue;
+
+              { add the type to the generic params }
+              genericparams.add(key,caller_param);
+            end;
+        end;
+
+      { specialize arrays by using element types but arrays may be multi-dimensional 
+        so we need to examine the caller/target pairs recursively in order to
+        verify the dimensionality is equal }
+      function handle_arrays(owner:tprocdef; target_def,caller_def:tarraydef; out target_element,caller_element:tdef): boolean;
+        begin
+          { the target and the caller are both arrays and the target is a 
+            specialization so we can recurse into the targets element def }
+          if is_array_literal(target_def.elementdef) and 
+            is_array_literal(caller_def.elementdef) and 
+            target_def.is_specialization then
+            result:=handle_arrays(owner,tarraydef(target_def.elementdef),tarraydef(caller_def.elementdef),target_element,caller_element)
+          else
+            begin
+              { the caller is an array which means the dimensionality is unbalanced
+                and thus the arrays are compatible }
+              if is_array_literal(caller_def.elementdef) then
+                exit(false);
+              { if the element is a generic param then return this type
+                along with the caller element type at the same level }
+              result:=owner.is_generic_param(target_def.elementdef);
+              if result then
+                begin
+                  target_element:=target_def.elementdef;
+                  caller_element:=caller_def.elementdef;
+                end;
+            end;
+        end;
+
+      { handle procvars by using the parameters from the caller to specialize
+        the parameters of the target generic procedure specialization. for example:
+
+          type generic TProc<S> = procedure(value: S);
+          generic procedure Run<T>(proc: specialize TProc<T>);
+          procedure DoCallback(value: integer);
+          Run(@DoCallback);
+
+        will specialize as Run<integer> because the signature
+        of DoCallback() matches TProc<S> so we can specialize "S"
+        with "integer", as they are both parameter #1
+      }
+
+      function handle_procvars(genericparams:tfphashlist; callerparams:tfplist; 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;
+          newparams: tfphashlist;
+          key: string;
+          index: integer;
+          valid_params: integer;
+        begin
+          result := false;
+
+          target_proc:=tprocvardef(target_def);
+          caller_proc:=tprocvardef(caller_def);
+
+          { parameter count must match exactly
+            currently default values are not considered }
+          if target_proc.paras.count<>caller_proc.paras.count then
+            exit;
+
+          { reject generics with constants }
+          for i:=0 to target_proc.genericdef.genericparas.count-1 do
+            if tsym(target_proc.genericdef.genericparas[i]).typ=constsym then
+              exit;
+
+          newparams:=tfphashlist.create;
+          valid_params:=0;
+
+          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
+                begin
+                  newparams.free;
+                  exit(false);
+                end;
+
+              if sp_generic_para in target_proc_para.vardef.typesym.symoptions 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);
+
+                  key:=generic_param_hash(target_def);
+
+                  { the generic param must not already be used }
+                  index:=genericparams.findindexof(key);
+                  if index=-1 then
+                    begin
+                      { add the type to the list }
+                      index:=newparams.findindexof(key);
+                      if index=-1 then
+                        newparams.add(key,caller_def.typesym);
+                    end;
+                end;
+
+              inc(valid_params);
+            end;
+
+          { if the count of valid params matches the target then
+            transfer the temporary params to the actual params }
+          result:=valid_params=target_proc.paras.count;
+          if result then
+            for i := 0 to newparams.count-1 do
+              genericparams.add(newparams.nameofindex(i),newparams[i]);
+
+          newparams.free;
+        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;
+          target_element,
+          caller_element: tdef;
+          required_param_count: integer;
+          adef: tarraydef;
+        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 target function parameters }
+          paras:=tfplist.create;
+          for i:=0 to genericdef.paras.count-1 do
+            begin
+              paravar:=tparavarsym(genericdef.paras[i]);
+              { ignore hidden parameters }
+              if vo_is_hidden_para in paravar.varoptions then
+                continue;
+              paras.add(paravar);
+
+              { const non-default parameters are required }
+              if paravar.defaultconstsym=nil then
+                inc(required_param_count);
+            end;
+
+          { 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(genericdef,ttypesym(genericdef.genericparas[i]),paras) then
+              inc(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
+                genericdef.is_generic_param(tarraydef(target_def).elementdef) then
+                begin
+                  target_def:=tarraydef(target_def).elementdef;
+                  target_key:=generic_param_hash(target_def);
+                  caller_def:=tstringdef(caller_def).get_default_char_type;
+                end
+              { non-uniform array constructors (i.e. array of const) are not compatible 
+                with normal arrays like "array of T" so we reject them }
+              else if is_array_literal(target_def) and
+                (caller_def.typ=arraydef) and 
+                (ado_IsConstructor in tarraydef(caller_def).arrayoptions) and
+                (ado_IsArrayOfConst in tarraydef(caller_def).arrayoptions) then
+                begin
+                  continue;
+                end
+              { handle generic arrays }
+              else if is_array_literal(caller_def) and
+                is_array_literal(target_def) and
+                handle_arrays(genericdef,tarraydef(target_def),tarraydef(caller_def),target_element,caller_element) then
+                begin
+                  target_def:=target_element;
+                  caller_def:=caller_element;
+                  target_key:=generic_param_hash(target_def);
+                end
+              { handle generic procvars }
+              else if (caller_def.typ=procvardef) and 
+                (target_def.typ=procvardef) and 
+                tprocvardef(target_def).is_specialization and
+                handle_procvars(genericparams,callerparams,target_def,caller_def) then
+                begin
+                  continue;
+                end
+              { handle specialized objects by taking the base class as the type to specialize }    
+              else if is_class_or_object(caller_def) and 
+                is_class_or_object(target_def) and
+                genericdef.is_generic_param(target_def) then
+                begin
+                  target_key:=generic_param_hash(target_def);
+                  target_def:=tobjectdef(target_def).childof;
+                end
+              { handle generic specializations }
+              else if tstoreddef(caller_def).is_specialization and 
+                tstoreddef(target_def).is_specialization and
+                (tstoreddef(caller_def).genericdef=tstoreddef(target_def).genericdef) then
+                begin
+                  handle_specializations(genericparams,tstoreddef(target_def),tstoreddef(caller_def));
+                  continue;
+                end
+              { handle all other generic params }
+              else if target_def.typ=undefineddef then
+                target_key:=generic_param_hash(target_def);
+
+              { 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>=0 then
+                continue;
+
+              { the caller type may not have a typesym so we need to create an unamed one }
+              if caller_def.typesym=nil then
+                begin
+                  sym:=create_unamed_typesym(caller_def);
+                  // TODO: sym is leaking memory!
+                  genericparams.add(target_key,sym);
+                end
+              else
+                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;
+        dummysym: tprocsym;
+        genericparams: tfphashlist;
+        spezcontext:tspecializationcontext;
+      begin
+        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]);
+                if is_possible_specialization(callerparams,pd,genericparams) then
+                  begin
+                    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/symdef.pas b/compiler/symdef.pas
index fca90803bf..d7b612b27b 100644
--- a/compiler/symdef.pas
+++ b/compiler/symdef.pas
@@ -721,6 +721,8 @@ interface
           procedure declared_far;virtual;
           procedure declared_near;virtual;
           function generate_safecall_wrapper: boolean; virtual;
+          { returns true if the def is a generic param of the procdef }
+          function is_generic_param(def:tdef): boolean;
        private
           procedure count_para(p:TObject;arg:pointer);
           procedure insert_para(p:TObject;arg:pointer);
@@ -1003,6 +1005,10 @@ interface
           function alignment : shortint;override;
           function  needs_inittable : boolean;override;
           function  getvardef:longint;override;
+          { returns the default char type def for the string }
+          function get_default_char_type: tdef;
+          { returns the default string type }
+          function get_default_string_type: tdef;
        end;
        tstringdefclass = class of tstringdef;
 
@@ -2746,6 +2752,39 @@ implementation
       end;
 
 
+    function tstringdef.get_default_char_type: tdef;
+      begin
+        case stringtype of
+          st_shortstring,
+          st_longstring,
+          st_ansistring:
+            result:=cansichartype;
+          st_unicodestring,
+          st_widestring:
+            result:=cwidechartype;   
+        end;
+      end;
+
+
+    function tstringdef.get_default_string_type: tdef;
+      begin
+        case stringtype of
+          st_shortstring:
+            result:=cshortstringtype;
+          { st_longstring is currently not supported but 
+            when it is this case will need to be supplied }
+          st_longstring:
+            internalerror(2021040801);
+          st_ansistring:
+            result:=cansistringtype;
+          st_widestring:
+            result:=cwidestringtype;
+          st_unicodestring:
+            result:=cunicodestringtype;
+        end
+      end;
+
+
     function tstringdef.alignment : shortint;
       begin
         case stringtype of
@@ -5983,6 +6022,17 @@ implementation
       end;
 
 
+    function tabstractprocdef.is_generic_param(def:tdef): boolean;
+      begin
+        if def.typ=undefineddef then
+          result:=def.owner=self.parast
+        else if def.typ=objectdef then
+          result:=(def.owner=self.parast) and (tstoreddef(def).genconstraintdata<>nil)
+        else
+          result:=false;
+      end;
+
+
 {***************************************************************************
                                   TPROCDEF
 ***************************************************************************}
diff --git a/compiler/symsym.pas b/compiler/symsym.pas
index 671933df07..f6064190ff 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;
@@ -1476,6 +1476,13 @@ implementation
       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
diff --git a/compiler/utils/ppuutils/ppudump.pp b/compiler/utils/ppuutils/ppudump.pp
index d96a79623c..c16e1c5c66 100644
--- a/compiler/utils/ppuutils/ppudump.pp
+++ b/compiler/utils/ppuutils/ppudump.pp
@@ -2406,7 +2406,8 @@ const
          '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 }
        );
        { optimizer }
        optimizerswitchname : array[toptimizerswitch] of string[50] =
patch-10.diff (45,502 bytes)   

Sven Barth

2021-04-19 09:31

manager   ~0130449

Last edited: 2021-04-19 09:31

View 2 revisions

Haven't looked at the new tests yet, but one thing that I still see that needs to be fixed are those two "memory leak" ToDos.

You essentially need to make sure the symbols become part of the specialization that is picked in the end (cause you create the symbols in make_param_list only once, but you might use them for multiple generic overloads of which at most one will be picked).
What you might be able to do is the following (have not looked at it in detail yet):
- those symbols you directly add to genericparams you ensure that their owner is the specialization right away (after generate_specialization_phase1)
- those symbols you add to callerparams you'll probably have to bubble up to htypechk and have that somehow add these syms to the final specialization if it is picked and free them otherwise

Ryan Joseph

2021-04-19 17:32

reporter   ~0130452

I looked at this again and it looks like there is just one place where we can leak memory now and that's those lines from create_unamed_typesym.

              newtype:=ctypesym.create(def.typename,def);
              newtype.owner:=def.owner;

I set the owner of the new typesym to the target def but what does that even do? I think you're saying I need to set the owner of that symbol to be a procdef but I don't see how the helps the memory get freed. I would think when the specialization is freed we could check some flag in the generic params and if they are not owned then we free them then.

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
2021-03-15 17:32 Ryan Joseph Note Added: 0129695
2021-03-15 17:32 Ryan Joseph File Added: patch-7.diff
2021-03-15 17:34 Ryan Joseph Note Edited: 0129695 View Revisions
2021-03-25 16:32 Ryan Joseph Note Added: 0129896
2021-03-26 15:32 Sven Barth Note Added: 0129910
2021-03-26 18:18 Ryan Joseph Note Added: 0129912
2021-04-02 21:46 Sven Barth Note Added: 0130051
2021-04-03 02:57 Ryan Joseph Note Added: 0130054
2021-04-03 11:43 Sven Barth Note Edited: 0130051 View Revisions
2021-04-03 11:46 Sven Barth Note Added: 0130058
2021-04-03 17:08 Ryan Joseph Note Added: 0130064
2021-04-06 18:03 Ryan Joseph Note Added: 0130140
2021-04-08 19:31 Ryan Joseph Note Added: 0130181
2021-04-08 19:31 Ryan Joseph File Added: final.zip
2021-04-08 19:31 Ryan Joseph File Added: patch-8.diff
2021-04-16 19:30 Ryan Joseph Note Added: 0130414
2021-04-16 19:30 Ryan Joseph File Added: patch-9.diff
2021-04-16 19:30 Ryan Joseph File Added: timpfuncspez33.pp
2021-04-17 12:57 Sven Barth Note Added: 0130424
2021-04-17 17:13 Ryan Joseph Note Added: 0130430
2021-04-17 17:46 Ryan Joseph Note Edited: 0130430 View Revisions
2021-04-18 13:49 Sven Barth Note Added: 0130440
2021-04-18 18:55 Ryan Joseph Note Added: 0130442
2021-04-18 18:55 Ryan Joseph File Added: final-2.zip
2021-04-18 18:55 Ryan Joseph File Added: patch-10.diff
2021-04-19 09:31 Sven Barth Note Added: 0130449
2021-04-19 09:31 Sven Barth Note Edited: 0130449 View Revisions
2021-04-19 17:32 Ryan Joseph Note Added: 0130452