[fpc-pascal] generic proc inference

Ryan Joseph genericptr at gmail.com
Mon Oct 7 00:16:16 CEST 2019



> On Oct 6, 2019, at 5:40 PM, Mattias Gaertner via fpc-pascal <fpc-pascal at lists.freepascal.org> wrote:
> 
>> Which test? Please post a sample.
> 
> generic procedure DoThis<T>(a:T; b:T);
> begin end;
> 
> begin
>  DoThis(1,200); // 1 sets T to shortint, so the 200 gives a warning
> end;
> 

What is the preferred behavior? I’m getting an error in ObjFPC mode.

{$mode objfpc}
{$modeswitch implicitfunctionspecialization}

program timpfuncspez16;

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

begin
  DoThis(1, 200);
end.

> 
>>> 3.
>>> timpfuncspez2.pp
>>> DoThis<T>
>>> DoThis<T,U>
>>> Delphi gives an error "Ambiguous call to DoThis". FPC silently
>>> selects the one with only one param. IMO this is dangerous, it
>>> should give an error.  
> 
> generic function DoThis<T>(a: T): T; overload;
> begin end;
> generic function DoThis<T,U>(a: T): U; overload;
> begin end;
> 
> begin
>  DoThis(3); // both fits, should give an error
> end;

This is debatable I think but I understand why "Ambiguous call to DoThis” would make sense. I’m pretty sure C# does this also.

What is the rule then? I’ll have to think about that some more.

> 
> 
>> [...]
>>> 4.
>>> Why does timpfuncspez6 fail?
>>> It works in Delphi.
>>> The comment has an explanation, which looks wrong to me:
>>> 
>>> 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.  
>> 
>> That doesn’t make sense to me either so I need to study it. Both
>> should fail actually as I designed it (for now).
> 
> Why?

I think my logic is different from what you expect though and I’m happy to change it if we need to.

There’s the comment I left in the code for the “try_implicit_specialization” function.

{ 	  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>
}


I think for this example my logic does: T = integer, U = TObject because the first 2 integers params are repeating so they both consume the single T and then the TObject consumes the U. That means the second param “1” is being passed for a TObject type. 

generic procedure DoThis<T,U>(msg: T; param1: U; param2: TObject);

DoThis(1, 1, TObject.Create); // DoThis(msg: integer; param1: TObject; param2: TObject);

I used this logic because it works for repeating patterns but it’s not very intuitive in cases where the arguments are the same number as the generic parameters.

What should be the rule here?

> 
> 
>> How does Delphi implicitly specialize this?
> 
> DoThis(1,1,nil); // T and U become shortint
> DoThis('aa','aa',nil); // T and U become string

So Delphi seems to match by index so param 1 = generic param 1. With repeating patterns this breaks downs but maybe it’s a special case if the function parameter count matches the generic parameter count?

Regards,
	Ryan Joseph



More information about the fpc-pascal mailing list