Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2018-11

Constants in generics

96 messages · 11 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

I implemented a first draft of constants (integers) in generics. My reason was specifically that I wanted a way to add methods to static arrays and generic records is the only way to accomplish this AFAIK.

If I fix this up will it be considered as a patch? I wanted to present the idea first before I spent any more time. Here’s what I has so far (on GitHub).

https://github.com/genericptr/freepascal/commit/ec518542b2da7d7f016702a82b2d05349a01a6fb

{$mode objfpc}
{$modeswitch advancedrecords}
program generic_constants;
type
	generic TList<T, U> = record
		list: array[0..U-1] of T;
		function capacity: integer;
	end;
function TList.capacity: integer;
begin
	result := U;
end;
var
	nums: specialize TList<integer,10>;
	strs: specialize TList<integer,4>;
begin
	writeln('sizeof:',sizeof(nums), ' capacity:',nums.capacity);
	writeln('sizeof:',sizeof(strs), ' capacity:',strs.capacity);
end.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Di., 6. Nov. 2018, 08:44 hat Ryan Joseph
geschrieben:

Show quoted text 35 lines
I implemented a first draft of constants (integers) in generics. My reason was specifically that I wanted a way to add methods to static arrays and generic records is the only way to accomplish this AFAIK. If I fix this up will it be considered as a patch? I wanted to present the idea first before I spent any more time. Here’s what I has so far (on GitHub). https://github.com/genericptr/freepascal/commit/ec518542b2da7d7f016702a82b2d05349a01a6fb {$mode objfpc} {$modeswitch advancedrecords} program generic_constants; type generic TList<T, U> = record list: array[0..U-1] of T; function capacity: integer; end; function TList.capacity: integer; begin result := U; end; var nums: specialize TList<integer,10>; strs: specialize TList<integer,4>; begin writeln('sizeof:',sizeof(nums), ' capacity:',nums.capacity); writeln('sizeof:',sizeof(strs), ' capacity:',strs.capacity); end.

First of I'm not a fan of adding support for constants, mainly because it will definitely not help parsing of inline specializations in mode Delphi which are going to be annoying enough already.
That said: even if we do add it, then only if a generic constant parameter is designated as such with "const N" in the generic declaration instead of merely "N", so that the compiler does not assume it's a type.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 6, 2018, at 8:21 PM, Sven Barth via fpc-pascal wrote: First of I'm not a fan of adding support for constants, mainly because it will definitely not help parsing of inline specializations in mode Delphi which are going to be annoying enough already.

Can you give an example? There’s lots of ways generics can be used and I tested in only a few.

This is a very important improvement because it allows us to extend static arrays in ways we haven’t been able to do before. Unless I missed something huge it seems like a simple thing to implement (see the minor changes I made in a single unit).

That said: even if we do add it, then only if a generic constant parameter is designated as such with "const N" in the generic declaration instead of merely "N", so that the compiler does not assume it's a type.

Why is this preferable? If you tried to pass a type for a constant in a specialization then you’d probably just get a type error which is easy to catch. Generics in FPC are already very verbose (“specialize" keyword is exhaustingly long) but adding “const” is at least a small cosmetic change in a single location.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Di., 6. Nov. 2018, 14:35 hat Ryan Joseph
geschrieben:

Show quoted text 12 lines
> On Nov 6, 2018, at 8:21 PM, Sven Barth via fpc-pascal < > wrote: > > First of I'm not a fan of adding support for constants, mainly because it will definitely not help parsing of inline specializations in mode Delphi which are going to be annoying enough already. Can you give an example? There’s lots of ways generics can be used and I tested in only a few.

Complex inline specializations containing, for example a multiplication with specializations on the left and the right side are currently not possible in mode Delphi. In addition to that Delphi allows overloads of generic types with variables and constants, so when the parser encounters "Bla<N" and has not yet encountered the ">" it does not know whether it needs to start a specialization or an expression. This is already annoying enough to solve with merely types, but if the right side of the "<" can also take constants for generics that gets downright proplematic.

Show quoted text 11 lines
> > That said: even if we do add it, then only if a generic constant parameter is designated as such with "const N" in the generic declaration instead of merely "N", so that the compiler does not assume it's a type. Why is this preferable? If you tried to pass a type for a constant in a specialization then you’d probably just get a type error which is easy to catch. Generics in FPC are already very verbose (“specialize" keyword is exhaustingly long) but adding “const” is at least a small cosmetic change in a single location.

Specialization is an expensive operation. When the compiler can already decide that "Something<SomeType, SomeOtherType>" can never be valid, because it's declared as "Something<T, const N>", then this is preferable. Otherwise the user might get some cryptic error message during the specialization, because "SomeOtherType" is used where only a constant can be used.
Pascal is considered a type safe language and without forcing users to decorate such parameters as "const" you essentially allow users to mix types and constants.
Not to mention that parsing the generic can already be done more correctly then it could be if the parameter is a typesym (which it would be right now).
You can't do e.g. "const SomeOtherConst = ConstParam * Pi" that way as it would mean to ease up the compiler's restrictions for type checking even more.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 6, 2018, at 11:19 PM, Sven Barth via fpc-pascal wrote: Complex inline specializations containing, for example a multiplication with specializations on the left and the right side are currently not possible in mode Delphi. In addition to that Delphi allows overloads of generic types with variables and constants, so when the parser encounters "Bla<N" and has not yet encountered the ">" it does not know whether it needs to start a specialization or an expression. This is already annoying enough to solve with merely types, but if the right side of the "<" can also take constants for generics that gets downright proplematic.

Can you show full a code example? If it really is so problematic because of something Delphi does then maybe it’s best left to objfpc mode until it can be resolved.

Specialization is an expensive operation. When the compiler can already decide that "Something<SomeType, SomeOtherType>" can never be valid, because it's declared as "Something<T, const N>", then this is preferable. Otherwise the user might get some cryptic error message during the specialization, because "SomeOtherType" is used where only a constant can be used. Pascal is considered a type safe language and without forcing users to decorate such parameters as "const" you essentially allow users to mix types and constants. Not to mention that parsing the generic can already be done more correctly then it could be if the parameter is a typesym (which it would be right now). You can't do e.g. "const SomeOtherConst = ConstParam * Pi" that way as it would mean to ease up the compiler's restrictions for type checking even more.

I guess if you include const it could save some compile time if you tried to specialize with the wrong type? It’s a pretty minor savings but that’s ok.

type
	generic TList<T,const U> = record
		list: array[0..U-1] of T;
		function capacity: integer;
	end;

Btw I only implemented this with integers (no strings or floats) because afaik this feature is only useful for arrays. I guess you could use non-ints for default parameters but this is probably a stupid idea because of how much compile time you’ll add to specialize a large amount of types.

Is there any reason to support strings and floats?

type
	generic TCaller<T,const U> = record
		procedure Say(message:T = U);
	end;
var
	caller: specialize TCaller<string,’hello’>;
begin
	caller.Say;

Regards,
Ryan Joseph

Martin
to Ryan Joseph: I implemented a first draft of constants (integers) in generics. My reason was…
Ryan Joseph wrote
I implemented a first draft of constants (integers) in generics. My reason was specifically that I wanted a way to add methods to static arrays and generic records is the only way to accomplish this AFAIK. If I fix this up will it be considered as a patch? I wanted to present the idea first before I spent any more time. Here’s what I has so far (on GitHub).

Just for info, there already is a way to do this, though being able to do it direct would be nice. You can wrap any constants into an object (or advanced record).

program Project1;
{$mode objfpc}{$H+}
uses  Classes;
type
   generic Foo<A> = object
     class procedure test;
   end;
   Data = object
     const val = 1;
   end;
var
   bar: specialize Foo<data>;
class procedure Foo.test;
begin
   writeln(A.val);
end;
begin
   bar.test;
   readln;
end.
Ryan Joseph
to denisgolovan

I added const in declarations so you get syntax error. I also disabled the const’s in delphi mode because it does indeed break inline specializations. Not sure how difficult that will be to fix or if it’s even worth it since this feature isn’t in delphi afaik.

https://github.com/genericptr/freepascal/tree/generic_constants

Pointless test to show the syntax:

{$mode objfpc}
{$modeswitch advancedrecords}
{$interfaces CORBA}
program generic_constants_interfaces;
type
	generic IMyInterface<T, const U> = interface
		procedure DoThis (value: T);
	end;
type
	generic THelper<T, const U> = record
		list: array[0..U-1] of T;
	end;
type
	generic TMyClass<T, const U> = class (specialize IMyInterface<T, U>)
		type
			// todo: second param in THelper must be const also!
			THelperType = specialize THelper<T, U>;
		public
			helper: THelperType;
			procedure DoThis (value: T);
	end;
	TListClass = specialize TMyClass<integer,10>;
procedure TMyClass.DoThis (value: T);
begin
	writeln('DoThis:',value, ' default: ', U);
end;
var
	c: TListClass;
begin
	c := TListClass.Create;
	c.helper.list[5] := 100;
	writeln(c.helper.list[5]);
	c.DoThis(100);
end.

Regards,
Ryan Joseph

Florian Klämpfl
to Ryan Joseph: I implemented a first draft of constants (integers) in generics. My reason was…
Ryan Joseph wrote
I implemented a first draft of constants (integers) in generics. My reason was specifically that I wanted a way to add methods to static arrays and generic records is the only way to accomplish this AFAIK. If I fix this up will it be considered as a patch? I wanted to present the idea first before I spent any more time. Here’s what I has so far (on GitHub).

I like the idea of const in generics, but it needs serious cleanup when it's working:
- commit messages like "first commit" are useless
- do not commit meta files like .gitignore with a functional commit
- follow the indention style of the surrounding code
- the compiler contains already a type called tgenericdef
- do not change the lazarus project files best practice is, to make a local copy for your own work (like ppcx64_ryan.lpi)
- create tests: succeeding as well as failing one
- do not use c style operators in the compiler, use inc/dec instead of += and -=
- replace comments like // note: ryan by comments like: "check for const generic paramters"

Ryan Joseph
to Florian Klämpfl
On Nov 9, 2018, at 4:28 AM, Florian Klämpfl wrote: I like the idea of const in generics, but it needs serious cleanup when it's working: - commit messages like "first commit" are useless

Those are for github so I could share but I need to learn SVN (again) eventually.

- do not commit meta files like .gitignore with a functional commit - follow the indention style of the surrounding code

Is the compiler 2 spaces for indent? There’s already enough inconsistency but I’m trying to figure it out.

- the compiler contains already a type called tgenericdef

I think I changed that name in the last commit. Btw I made all those types because I didn’t want to populate the main types like ttypesym with extra bytes but I don’t know what’s best practice in the codebase.

- do not change the lazarus project files best practice is, to make a local copy for your own work (like ppcx64_ryan.lpi)

Good idea, thanks.

- create tests: succeeding as well as failing one

ok.

- do not use c style operators in the compiler, use inc/dec instead of += and -=

ok.

- replace comments like // note: ryan by comments like: "check for const generic paramters”

those are temporary for my use. I’ll remove those of course.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph: Those are for github so I could share but I need to learn SVN (again) eventually. Is the…

Am Fr., 9. Nov. 2018, 03:44 hat Ryan Joseph
geschrieben:

Show quoted text 12 lines
> On Nov 9, 2018, at 4:28 AM, Florian Klämpfl wrote: > > I like the idea of const in generics, but it needs serious cleanup when it's working: > - commit messages like "first commit" are useless Those are for github so I could share but I need to learn SVN (again) eventually.

You can also use git svn. At least I myself use that for my own work (I think Florian does, too). Of course you need to set up syncing with your GitHub Repo as well, but I haven't done that for a SVN Repo yet...

Show quoted text 6 lines
> - do not commit meta files like .gitignore with a functional commit > - follow the indention style of the surrounding code Is the compiler 2 spaces for indent? There’s already enough inconsistency but I’m trying to figure it out.

Yes, two spaces. And "begin... end" is indented and its content is indented again.

Show quoted text 6 lines
> - the compiler contains already a type called tgenericdef I think I changed that name in the last commit. Btw I made all those types because I didn’t want to populate the main types like ttypesym with extra bytes but I don’t know what’s best practice in the codebase.

When something belongs in a main type then it belongs there. But as I already wrote it should not be a ttypesym for constant generic parameters anyway.

Regards,
Sven

Florian Klämpfl
to Ryan Joseph: Question: should other consts besides integers be allowed? I don’t think it makes sense…
Ryan Joseph wrote 7 lines
> On Nov 9, 2018, at 4:28 AM, Florian Klämpfl wrote: > > I like the idea of const in generics, but it needs serious cleanup when it's working: Question: should other consts besides integers be allowed? I don’t think it makes sense personally to use strings, floats or sets but maybe I’m wrong.

I would allow them for orthogonality reasons.

Florian Klämpfl
to Ryan Joseph: Those are for github so I could share but I need to learn SVN (again) eventually. Is the…
Ryan Joseph wrote 8 lines
> On Nov 9, 2018, at 4:28 AM, Florian Klämpfl wrote: > > I like the idea of const in generics, but it needs serious cleanup when it's working: > - commit messages like "first commit" are useless Those are for github so I could share but I need to learn SVN (again) eventually.

This does not prevent you from creating proper commits, especially as the patch series from github could be easily applied later on to fpc's main repository.

> - the compiler contains already a type called tgenericdef I think I changed that name in the last commit. Btw I made all those types because I didn’t want to populate the main types like ttypesym with extra bytes but I don’t know what’s best practice in the codebase.

Priority is:
1. maintainability
2. portability
3. speed

Keep also ppu storing/loading in mind.

Ryan Joseph
to Florian Klämpfl
On Nov 10, 2018, at 7:16 PM, Florian Klämpfl wrote: This does not prevent you from creating proper commits, especially as the patch series from github could be easily applied later on to fpc's main repository.

I’ll assume these commits could be public some day then. I originally thought I would have to trash them all and start over for svn anyways.

Show quoted text 12 lines
> >> - the compiler contains already a type called tgenericdef > > I think I changed that name in the last commit. Btw I made all those types because I didn’t want to populate the main types like ttypesym with extra bytes but I don’t know what’s best practice in the codebase. Priority is: 1. maintainability 2. portability 3. speed Keep also ppu storing/loading in mind.

Thus far on the compiler I haven’t encountered PPU's at all so I just assumed they were part of the code generator and I didn’t need to worry about them. How are they relevant here? Honestly I barely know what the PPU’s are and how the compiler uses them.

Regards,
Ryan Joseph

Florian Klämpfl
to Ryan Joseph
Ryan Joseph wrote 14 lines
>> >>> - the compiler contains already a type called tgenericdef >> >> I think I changed that name in the last commit. Btw I made all those types because I didn’t want to populate the main types like ttypesym with extra bytes but I don’t know what’s best practice in the codebase. > > Priority is: > 1. maintainability > 2. portability > 3. speed > > Keep also ppu storing/loading in mind. Thus far on the compiler I haven’t encountered PPU's at all so I just assumed they were part of the code generator and I didn’t need to worry about them. How are they relevant here? Honestly I barely know what the PPU’s are and how the compiler uses them.

If you "export" a generic taking a const from a unit, this info has to be stored in the ppu.

Ryan Joseph
to Florian Klämpfl
On Nov 10, 2018, at 7:22 PM, Florian Klämpfl wrote: If you "export" a generic taking a const from a unit, this info has to be stored in the ppu.

Can you show a test case I could use to see where this is triggered in the compiler? I don’t even know where to begin looking.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Sa., 10. Nov. 2018, 14:47 hat Ryan Joseph
geschrieben:

Show quoted text 11 lines
> On Nov 10, 2018, at 7:22 PM, Florian Klämpfl wrote: > > If you "export" a generic taking a const from a unit, this info has to be stored in the ppu. Can you show a test case I could use to see where this is triggered in the compiler? I don’t even know where to begin looking.

The important part are the ppuwrite and ppuload methods that are provided by tstoreddef, tstoredsym and tstoredsymtable and overridden by most of their descendants. E.g. tstoreddef itself is also dealing with the generic parameters.

And for a test: declare the generic in one unit, specialize it in another and compile twice. Usually things go boom if you messed up something.

By the way: When adding/changing data that is written to the PPU increase the PPUVersion constant in the ppu.pas unit (there is no backwards compatibility for PPUs) and also adjust the ppudump utility in compiler/utils/ppudump.

Regards,
Sven

Ben Grasset
to Ryan Joseph: Question: should other consts besides integers be allowed? I don’t think it makes sense…
PM Ryan Joseph wrote
Question: should other consts besides integers be allowed? I don’t think it makes sense personally to use strings, floats or sets but maybe I’m wrong.

As a daily FPC user there's nothing I find more annoying than compiler restrictions that are visibly artificial, so I'd say you should definitely allow basically anything that's physically possible to allow in that context.
*Someone* will find it useful, trust me. I can already think of various uses for string constants as generic parameters that I certainly would have already done in the past if I could have, for example.

PM Ryan Joseph wrote 18 lines
> On Nov 9, 2018, at 4:28 AM, Florian Klämpfl wrote: > > I like the idea of const in generics, but it needs serious cleanup when it's working: Question: should other consts besides integers be allowed? I don’t think it makes sense personally to use strings, floats or sets but maybe I’m wrong. Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Ryan Joseph
to Ben Grasset

I think I have this done except for cleanup. Here’s the status:

- Integer,string,real,set and nil constant for parameters.
- Consts can have type restrictions which correspond to the above types.
- Const can be assigned to from generic const params.
- PPU loading.

Technical compiler issues:

- I couldn’t figure out how to extend PPU loading for a ttypesym subclass so I put 2 fields into ttypesym. Those are probably best moved to a subclass later.
- We may need new error messages but I just left place holders in for now.

https://github.com/genericptr/freepascal/tree/generic_constants

program generic_constants_types;
type
	generic TList<T, const U:integer> = record
		const
			ID = U;
		public
			list: array[0..U-1] of T;
			procedure dothis (msg:string);
	end;
procedure TList.dothis (msg:string);
begin
	writeln('message:',msg, ' high:', high(ID), ' constsize:', sizeof(ID), ' structsize:', sizeof(self));
end;
type
	TDay = (Mon, Tue, Wed);
	TDays = set of TDay;
const
	kSomeDays:TDays = [Mon, Wed];
var
	a: specialize TList<integer,10>;
	//b: specialize TList<integer,'foo'>;
	//c: specialize TList<integer,0.1>;
	//d: specialize TList<integer,kSomeDays>;
	//e: specialize TList<integer,nil>;
begin
	a.dothis('hello');
end.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
Ryan Joseph wrote 13 lines
I think I have this done except for cleanup. Here’s the status: - Integer,string,real,set and nil constant for parameters. - Consts can have type restrictions which correspond to the above types. - Const can be assigned to from generic const params. - PPU loading. Technical compiler issues: - I couldn’t figure out how to extend PPU loading for a ttypesym subclass so I put 2 fields into ttypesym. Those are probably best moved to a subclass later. - We may need new error messages but I just left place holders in for now. https://github.com/genericptr/freepascal/tree/generic_constants

In contrast to the multiple helper ones I have much more to complain about here:

- your pretty name is wrong; the pretty name for a specialization with constants should be "TSomeGeneric<TSomeType, 42>", not "TSomeGeneric<SomeUnit.TSomeType, System.LongInt#42>" as it would be now
- remove those tgenericparasym and tgeneric_*_parasym types; use simply tconstsym instead of ttypesym for const parameters, other code will have to check that correctly
- even though you'll remove is_const and const_type from ttypesym again due to the above it's wrong to use putsmallset/getsmallset for an enum; use putbyte/getbyte or one of the "larger" put*/get* functions depending on the number of entries contained in the enum (Note: not a runtime check, just pick the correct one at compile time)
- get rid of tscannerfile.parsing_generic_type; it's not only at the wrong location (the name "parsing_generic_type" should already tell you that it has no place inside the *scanner*), but it's also used to solve something in a completely wrong way: you must *not* use current_structdef for the generic parameters as the only valid parameter list inside parse_generic_specialization_types_internal *is* paradeflist and genericdef.genericparas. If there is still a problem then you need to handle that differently (Note: also don't Exit from parse_generic_specialization_type_internal, because the idea is that all incompatibilities are shown at once, so use Continue to check the next parameter)
- remove the check for m_objfpc; yes it won't work with inline specializations in mode Delphi for now, but it can still be used for specializations in type or var sections; also one idea for an improvement I have for parsing inline specializations in mode Delphi would be if the compiler knows that the symbol left of the "<" can only be a generic (cause let's be honest: in most code types aren't overloaded with variables/constants and more often than not types begin with "T", so they shouldn't conflict with the names of more local variables/constants/fields either)

Also thinking about the feature a bit it would be better to enforce the type of the constant during the generic's declaration. After all I can do different things with a string const, an ordinal const or a set const. So it would be better to use "const <NAME>: <TYPE>" where type can be any type that results in a tconstsym. After all the type of the constant inside the generic is usually fixed and those few cases were a variadic constant would be necessary could be handled by "TMyGeneric<T; const N: T>" (which would not work right now, but as we also need to support "TMyGeneric<T; S: SomeIntf<T>>" due to Delphi compatibility that would be handled by the same solution).
This way you can also get rid of the cconstundefined and you can create a correct tconstsym right of the bat.

And as I had written for the helper extension: add tests. A feature like this will need many tests (you can use "tgenconst" as test name prefix). Also don't forget to test this with generic routines.

Don't forget to check the code formatting as well, I saw a few locations where you had spaces where they don't belong. ;)

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 26, 2018, at 12:09 AM, Sven Barth via fpc-pascal wrote: - your pretty name is wrong; the pretty name for a specialization with constants should be "TSomeGeneric<TSomeType, 42>", not "TSomeGeneric<SomeUnit.TSomeType, System.LongInt#42>" as it would be now

I’ll change the # prefix for const params but the unit prefix was not added by me. See the line:

prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true);

in pgenutil.pas.

- remove those tgenericparasym and tgeneric_*_parasym types; use simply tconstsym instead of ttypesym for const parameters, other code will have to check that correctly

Ok, I was able to remove those and replace with tconstsym. Much cleaner now.

- even though you'll remove is_const and const_type from ttypesym again due to the above it's wrong to use putsmallset/getsmallset for an enum; use putbyte/getbyte or one of the "larger" put*/get* functions depending on the number of entries contained in the enum (Note: not a runtime check, just pick the correct one at compile time)

Are you sure those should be changed? There seems to be lots of assumptions made that the generic params are ttypesym and I’m not confident that mixing in tconstsyms will be safe. Having said that I never liked that I mixed in those const_* members into the class.

Like you mention below if the const type was restricted in the definition then it would make sense to change these to tconstsym. I guess I need to try and see how much code this change blows up.

- get rid of tscannerfile.parsing_generic_type; it's not only at the wrong location (the name "parsing_generic_type" should already tell you that it has no place inside the *scanner*), but it's also used to solve something in a completely wrong way: you must *not* use current_structdef for the generic parameters as the only valid parameter list inside parse_generic_specialization_types_internal *is* paradeflist and genericdef.genericparas. If there is still a problem then you need to handle that differently (Note: also don't Exit from parse_generic_specialization_type_internal, because the idea is that all incompatibilities are shown at once, so use Continue to check the next parameter)

Yes, that was a hack to access current_structdef safely but I guess that’s not what I should be doing. Let me see if I can get that same information from the 2 params you mentioned.

- remove the check for m_objfpc; yes it won't work with inline specializations in mode Delphi for now, but it can still be used for specializations in type or var sections; also one idea for an improvement I have for parsing inline specializations in mode Delphi would be if the compiler knows that the symbol left of the "<" can only be a generic (cause let's be honest: in most code types aren't overloaded with variables/constants and more often than not types begin with "T", so they shouldn't conflict with the names of more local variables/constants/fields either)

I got a crash in Delphi mode but I can change that to an error.

Also thinking about the feature a bit it would be better to enforce the type of the constant during the generic's declaration. After all I can do different things with a string const, an ordinal const or a set const. So it would be better to use "const <NAME>: <TYPE>" where type can be any type that results in a tconstsym. After all the type of the constant inside the generic is usually fixed and those few cases were a variadic constant would be necessary could be handled by "TMyGeneric<T; const N: T>" (which would not work right now, but as we also need to support "TMyGeneric<T; S: SomeIntf<T>>" due to Delphi compatibility that would be handled by the same solution). This way you can also get rid of the cconstundefined and you can create a correct tconstsym right of the bat.

Sure but I need to see how many assumptions this undoes in other areas. I seem to remember having problems getting the const to fit in as it was and that’s when they were all the same class.

And as I had written for the helper extension: add tests. A feature like this will need many tests (you can use "tgenconst" as test name prefix). Also don't forget to test this with generic routines. Don't forget to check the code formatting as well, I saw a few locations where you had spaces where they don't belong. ;)

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Mo., 26. Nov. 2018, 10:46 hat Ryan Joseph
geschrieben:

Show quoted text 16 lines
> On Nov 26, 2018, at 12:09 AM, Sven Barth via fpc-pascal < > wrote: > > - your pretty name is wrong; the pretty name for a specialization with constants should be "TSomeGeneric<TSomeType, 42>", not "TSomeGeneric<SomeUnit.TSomeType, System.LongInt#42>" as it would be now I’ll change the # prefix for const params but the unit prefix was not added by me. See the line: prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true); in pgenutil.pas.

Yes, but that is only because the code currently only handles types. For constants the addition of the type is not required, only the constant value.

Show quoted text 22 lines
> - remove those tgenericparasym and tgeneric_*_parasym types; use simply tconstsym instead of ttypesym for const parameters, other code will have to check that correctly Ok, I was able to remove those and replace with tconstsym. Much cleaner now. > - even though you'll remove is_const and const_type from ttypesym again due to the above it's wrong to use putsmallset/getsmallset for an enum; use putbyte/getbyte or one of the "larger" put*/get* functions depending on the number of entries contained in the enum (Note: not a runtime check, just pick the correct one at compile time) Are you sure those should be changed? There seems to be lots of assumptions made that the generic params are ttypesym and I’m not confident that mixing in tconstsyms will be safe. Having said that I never liked that I mixed in those const_* members into the class. Like you mention below if the const type was restricted in the definition then it would make sense to change these to tconstsym. I guess I need to try and see how much code this change blows up.

Those assumptions will "simply" have to be checked/fixed. In the end we'll have cleaner code and thus it will be worth it. And with the help of the testsuite you can avoid regressions. :)

Show quoted text 16 lines
> - get rid of tscannerfile.parsing_generic_type; it's not only at the wrong location (the name "parsing_generic_type" should already tell you that it has no place inside the *scanner*), but it's also used to solve something in a completely wrong way: you must *not* use current_structdef for the generic parameters as the only valid parameter list inside parse_generic_specialization_types_internal *is* paradeflist and genericdef.genericparas. If there is still a problem then you need to handle that differently (Note: also don't Exit from parse_generic_specialization_type_internal, because the idea is that all incompatibilities are shown at once, so use Continue to check the next parameter) Yes, that was a hack to access current_structdef safely but I guess that’s not what I should be doing. Let me see if I can get that same information from the 2 params you mentioned.

Maybe you can show a test case that is failing?

Show quoted text 12 lines
> - remove the check for m_objfpc; yes it won't work with inline specializations in mode Delphi for now, but it can still be used for specializations in type or var sections; also one idea for an improvement I have for parsing inline specializations in mode Delphi would be if the compiler knows that the symbol left of the "<" can only be a generic (cause let's be honest: in most code types aren't overloaded with variables/constants and more often than not types begin with "T", so they shouldn't conflict with the names of more local variables/constants/fields either) I got a crash in Delphi mode but I can change that to an error.

For now we should reject an inline specialization (aka block_type = bt_general) in mode Delphi if the found generic contains any constant parameter (declarations of such generics should be allowed). Later on we can lift that restriction once I've implemented the checks I mentioned.

Show quoted text 19 lines
> > Also thinking about the feature a bit it would be better to enforce the type of the constant during the generic's declaration. After all I can do different things with a string const, an ordinal const or a set const. So it would be better to use "const <NAME>: <TYPE>" where type can be any type that results in a tconstsym. After all the type of the constant inside the generic is usually fixed and those few cases were a variadic constant would be necessary could be handled by "TMyGeneric<T; const N: T>" (which would not work right now, but as we also need to support "TMyGeneric<T; S: SomeIntf<T>>" due to Delphi compatibility that would be handled by the same solution). > This way you can also get rid of the cconstundefined and you can create a correct tconstsym right of the bat. Sure but I need to see how many assumptions this undoes in other areas. I seem to remember having problems getting the const to fit in as it was and that’s when they were all the same class.

As I said above that code will have to become aware of constants. Might lead to more crashes for you right now, but the end result would be cleaner/better. As a help you could compile the compiler with -CR which would ensure that typecasts of classes are always done with "as", thus triggering an exception at the correct location.

Show quoted text 8 lines
> > And as I had written for the helper extension: add tests. A feature like this will need many tests (you can use "tgenconst" as test name prefix). Also don't forget to test this with generic routines. > > Don't forget to check the code formatting as well, I saw a few locations where you had spaces where they don't belong. ;)

Regards,
Sven

Ryan Joseph
to Sven Barth
Show quoted text 16 lines
On Nov 26, 2018, at 5:16 PM, Sven Barth via fpc-pascal wrote: Am Mo., 26. Nov. 2018, 10:46 hat Ryan Joseph geschrieben: > On Nov 26, 2018, at 12:09 AM, Sven Barth via fpc-pascal wrote: > > - your pretty name is wrong; the pretty name for a specialization with constants should be "TSomeGeneric<TSomeType, 42>", not "TSomeGeneric<SomeUnit.TSomeType, System.LongInt#42>" as it would be now I’ll change the # prefix for const params but the unit prefix was not added by me. See the line: prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true); in pgenutil.pas. Yes, but that is only because the code currently only handles types. For constants the addition of the type is not required, only the constant value.

Then is it ok if type params have the unit prefix? Right now I’m doing TGeneric<Unit.Type,40> because the first param is a type so it uses the original code.

Like you mention below if the const type was restricted in the definition then it would make sense to change these to tconstsym. I guess I need to try and see how much code this change blows up. Those assumptions will "simply" have to be checked/fixed. In the end we'll have cleaner code and thus it will be worth it. And with the help of the testsuite you can avoid regressions. :)

Ok, I’ll make it happen if it’s important.

Show quoted text 7 lines
> - get rid of tscannerfile.parsing_generic_type; it's not only at the wrong location (the name "parsing_generic_type" should already tell you that it has no place inside the *scanner*), but it's also used to solve something in a completely wrong way: you must *not* use current_structdef for the generic parameters as the only valid parameter list inside parse_generic_specialization_types_internal *is* paradeflist and genericdef.genericparas. If there is still a problem then you need to handle that differently (Note: also don't Exit from parse_generic_specialization_type_internal, because the idea is that all incompatibilities are shown at once, so use Continue to check the next parameter) Yes, that was a hack to access current_structdef safely but I guess that’s not what I should be doing. Let me see if I can get that same information from the 2 params you mentioned. Maybe you can show a test case that is failing?

This is why I wanted to access current_structdef. When "specialize IMyInterface<T, U>” was being parsed I need a way to refer back to the generic so I know what “U” is.

type
	generic TMyClass<T, const U> = class (specialize IMyInterface<T, U>)
		type
			THelperType = specialize THelper<T, U>;
		public
			helper: THelperType;
			procedure DoThis (value: T);
	end;
	TListClass = specialize TMyClass<integer,10>;
Show quoted text 7 lines
> - remove the check for m_objfpc; yes it won't work with inline specializations in mode Delphi for now, but it can still be used for specializations in type or var sections; also one idea for an improvement I have for parsing inline specializations in mode Delphi would be if the compiler knows that the symbol left of the "<" can only be a generic (cause let's be honest: in most code types aren't overloaded with variables/constants and more often than not types begin with "T", so they shouldn't conflict with the names of more local variables/constants/fields either) I got a crash in Delphi mode but I can change that to an error. For now we should reject an inline specialization (aka block_type = bt_general) in mode Delphi if the found generic contains any constant parameter (declarations of such generics should be allowed). Later on we can lift that restriction once I've implemented the checks I mentioned.

Ok, I can give an error is this one instance where it fails and allow all the others.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Mo., 26. Nov. 2018, 12:14 hat Ryan Joseph
geschrieben:

Show quoted text 31 lines
> On Nov 26, 2018, at 5:16 PM, Sven Barth via fpc-pascal < > wrote: > > Am Mo., 26. Nov. 2018, 10:46 hat Ryan Joseph geschrieben: > > > > On Nov 26, 2018, at 12:09 AM, Sven Barth via fpc-pascal < > wrote: > > > > - your pretty name is wrong; the pretty name for a specialization with constants should be "TSomeGeneric<TSomeType, 42>", not "TSomeGeneric<SomeUnit.TSomeType, System.LongInt#42>" as it would be now > > I’ll change the # prefix for const params but the unit prefix was not added by me. See the line: > > prettynamepart:=typeparam.resultdef.fullownerhierarchyname(true); > > in pgenutil.pas. > > Yes, but that is only because the code currently only handles types. For constants the addition of the type is not required, only the constant value. Then is it ok if type params have the unit prefix? Right now I’m doing TGeneric<Unit.Type,40> because the first param is a type so it uses the original code.

Yes. Type parameters have their full name so that one can differentiate between Unit1.TType and Unit2.TType or Unit1.TSomeClass.TType and Unit2.TSomeClass.TType in error messages.

Show quoted text 45 lines
> Like you mention below if the const type was restricted in the definition then it would make sense to change these to tconstsym. I guess I need to try and see how much code this change blows up. > > Those assumptions will "simply" have to be checked/fixed. In the end we'll have cleaner code and thus it will be worth it. And with the help of the testsuite you can avoid regressions. :) Ok, I’ll make it happen if it’s important. > > > > - get rid of tscannerfile.parsing_generic_type; it's not only at the wrong location (the name "parsing_generic_type" should already tell you that it has no place inside the *scanner*), but it's also used to solve something in a completely wrong way: you must *not* use current_structdef for the generic parameters as the only valid parameter list inside parse_generic_specialization_types_internal *is* paradeflist and genericdef.genericparas. If there is still a problem then you need to handle that differently (Note: also don't Exit from parse_generic_specialization_type_internal, because the idea is that all incompatibilities are shown at once, so use Continue to check the next parameter) > > Yes, that was a hack to access current_structdef safely but I guess that’s not what I should be doing. Let me see if I can get that same information from the 2 params you mentioned. > > Maybe you can show a test case that is failing? This is why I wanted to access current_structdef. When "specialize IMyInterface<T, U>” was being parsed I need a way to refer back to the generic so I know what “U” is. type generic TMyClass<T, const U> = class (specialize IMyInterface<T, U>) type THelperType = specialize THelper<T, U>; public helper: THelperType; procedure DoThis (value: T); end; TListClass = specialize TMyClass<integer,10>;

You don't need to manually check for U. The parser will find U in the symbol table of the TMyClass generic and then a list containing the parameters will be generated and passed to
parse_generic_specialization_type_internal.
Though we'll probably either have to adjust the type of the paradeflist to carry the type/constant sym instead of the Def or an additional list that contains the constant symbols (plus NILs for the type ones). I currently favor the first one, which will mean a few more adjustments inside pgenutil when parsing specialization parameters.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 26, 2018, at 8:18 PM, Sven Barth via fpc-pascal wrote: You don't need to manually check for U. The parser will find U in the symbol table of the TMyClass generic and then a list containing the parameters will be generated and passed to parse_generic_specialization_type_internal. Though we'll probably either have to adjust the type of the paradeflist to carry the type/constant sym instead of the Def or an additional list that contains the constant symbols (plus NILs for the type ones). I currently favor the first one, which will mean a few more adjustments inside pgenutil when parsing specialization parameters.

I get a "class type expected, but got "<erroneous type>”” on the “U” in the nested specialization so I assumed this was because the hidden constant “U” was getting parsed instead of the generic param type “U”. I need to look at this more closely tomorrow because I don’t really understand what’s happening to be honest.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Mo., 26. Nov. 2018, 15:47 hat Ryan Joseph
geschrieben:

Show quoted text 21 lines
> On Nov 26, 2018, at 8:18 PM, Sven Barth via fpc-pascal < > wrote: > > You don't need to manually check for U. The parser will find U in the symbol table of the TMyClass generic and then a list containing the parameters will be generated and passed to parse_generic_specialization_type_internal. Though we'll probably either have to adjust the type of the paradeflist to carry the type/constant sym instead of the Def or an additional list that contains the constant symbols (plus NILs for the type ones). > I currently favor the first one, which will mean a few more adjustments inside pgenutil when parsing specialization parameters. I get a "class type expected, but got "<erroneous type>”” on the “U” in the nested specialization so I assumed this was because the hidden constant “U” was getting parsed instead of the generic param type “U”. I need to look at this more closely tomorrow because I don’t really understand what’s happening to be honest.

Best check again once you've done the switch to tconstsym for constants. :)

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 27, 2018, at 4:59 AM, Sven Barth via fpc-pascal wrote: Best check again once you've done the switch to tconstsym for constants. :)

I made most of the changes you mentioned so please look. Including the constsyms didn’t break anything and helped to clean up the code. I would have done that from the start but I wasn’t sure you wanted me mixing in new types.

Some things remaining:

1) kSomeDays is a typed const so I get a load node instead of tsetconstnode, which I need to need to know in order to make the name string. See the TODO’s in tgenericparamdef.create.

type
	TDay = (Mon, Tue, Wed);
	TDays = set of TDay;
const
	kSomeDays:TDays = [Mon, Wed];
var
	d: specialize TMyRecord_Set<integer,kSomeDays>; // this doesn’t work
	d: specialize TMyRecord_Set<integer,[Mon,Wed]>; // this works because I get a set node

2) Again in tgenericparamdef.create I don’t know how to make the pretty name string from a set (see the TODO)

3) You may be right about forcing the const still kept the undefined const placeholder in there but if you have nested specializations you can get type errors because the placeholder uses a nil node. It’s too early to know if there’s any reason for undefined consts.

4) I’m not sure if changing the types to consts messed up PPU loading or it works out of the box. Seems to be ok from initial tests though.

Regards,
Ryan Joseph

Ryan Joseph
to denisgolovan
Show quoted text 8 lines
On Nov 27, 2018, at 2:25 PM, denisgolovan wrote: Hi Sorry for breaking in, but I'd like to know if this functionality supports specializing generic functions with const parameters? BR, Denis

Here’s my test which seems to be working.

program gc_procs;
generic procedure DoThis<T,const U:string>(msg:string = U);
begin
	writeln(msg, ' ',sizeof(T), ' ', U);
end;
begin
	specialize DoThis<integer,'foo'>('hello world’);	// prints "hello world 4 foo"
	specialize DoThis<integer,'foo’>;			// prints “foo 4 foo"
end.

Regards,
Ryan Joseph

denisgolovan
to Ryan Joseph
Show quoted text 13 lines
Here’s my test which seems to be working. program gc_procs; generic procedure DoThis<T,const U:string>(msg:string = U); begin writeln(msg, ' ',sizeof(T), ' ', U); end; begin specialize DoThis<integer,'foo'>('hello world’); // prints "hello world 4 foo" specialize DoThis<integer,'foo’>; // prints “foo 4 foo" end.

Thanks.
That's definitely a nice feature.

Regards, Denis Golovan
Sven Barth
to Ryan Joseph: I made most of the changes you mentioned so please look. Including the constsyms didn’t…

Am Di., 27. Nov. 2018, 08:18 hat Ryan Joseph
geschrieben:

Show quoted text 14 lines
> On Nov 27, 2018, at 4:59 AM, Sven Barth via fpc-pascal < > wrote: > > Best check again once you've done the switch to tconstsym for constants. :) > I made most of the changes you mentioned so please look. Including the constsyms didn’t break anything and helped to clean up the code. I would have done that from the start but I wasn’t sure you wanted me mixing in new types.

Good, thank you. Hopefully I'll find the time this evening to look at your changes again. :)

Show quoted text 19 lines
Some things remaining: 1) kSomeDays is a typed const so I get a load node instead of tsetconstnode, which I need to need to know in order to make the name string. See the TODO’s in tgenericparamdef.create. type TDay = (Mon, Tue, Wed); TDays = set of TDay; const kSomeDays:TDays = [Mon, Wed]; var d: specialize TMyRecord_Set<integer,kSomeDays>; // this doesn’t work d: specialize TMyRecord_Set<integer,[Mon,Wed]>; // this works because I get a set node

The difference between typed and untyped constants are available everywhere else in the language as well, so I have no qualms (at least for now?) to error out on them.

For sets I believe that this should work however (see here: https://freepascal.org/docs-html/current/ref/refse9.html#x21-200002.1 ):

=== code begin ===
const
  kSomeDays = [Mon, Wed];
=== code end ===

2) Again in tgenericparamdef.create I don’t know how to make the pretty

name string from a set (see the TODO)

I'll need to take a we'll need to add a list of
elements to the set comet type if it isn't already there. 🤷‍♀️

3) You may be right about forcing the const still

Show quoted text 7 lines
kept the undefined const placeholder in there but if you have nested specializations you can get type errors because the placeholder uses a nil node. It’s too early to know if there’s any reason for undefined consts. 4) I’m not sure if changing the types to consts messed up PPU loading or it works out of the box. Seems to be ok from initial tests though.

Did you try with the generic and the specialization in different units and changing only one of the two? Though you might want to check whether tstoreddef.ppuload (or wherever tdef.genericparas is initialized after a unit load) correctly instantiated the tconstsym instances instead of ttypesym ones. It might already work correctly, but better safe than sorry. 😅

Regards,
Sven

Mattias Gaertner
to Sven Barth
Show quoted text 7 lines
[...] > const > kSomeDays:TDays = [Mon, Wed]; [...] The difference between typed and untyped constants are available everywhere else in the language as well, so I have no qualms (at least for now?) to error out on them.

What about

{$WritableConst off}
const kSomeDays:TDays = [Mon, Wed];

?
BTW, how do you name this? "proper typed const"?

Mattias

Ryan Joseph
to Sven Barth: Am Di., 27. Nov. 2018, 08:18 hat Ryan Joseph geschrieben: Good, thank you. Hopefully I'll…
Show quoted text 16 lines
On Nov 27, 2018, at 5:35 PM, Sven Barth via fpc-pascal wrote: 1) kSomeDays is a typed const so I get a load node instead of tsetconstnode, which I need to need to know in order to make the name string. See the TODO’s in tgenericparamdef.create. type TDay = (Mon, Tue, Wed); TDays = set of TDay; const kSomeDays:TDays = [Mon, Wed]; var d: specialize TMyRecord_Set<integer,kSomeDays>; // this doesn’t work d: specialize TMyRecord_Set<integer,[Mon,Wed]>; // this works because I get a set node The difference between typed and untyped constants are available everywhere else in the language as well, so I have no qualms (at least for now?) to error out on them.

You mean reject them? The default behavior is to get a type_e_type_id_expected error. They can be included easily but I don’t know how to get the set from the load node (I think it needs further parsing).

Show quoted text 7 lines
For sets I believe that this should work however (see here: https://freepascal.org/docs-html/current/ref/refse9.html#x21-200002.1 ): === code begin === const kSomeDays = [Mon, Wed]; === code end ===

Yes, sets work because I get a setnode which I can operate on.

Did you try with the generic and the specialization in different units and changing only one of the two? Though you might want to check whether tstoreddef.ppuload (or wherever tdef.genericparas is initialized after a unit load) correctly instantiated the tconstsym instances instead of ttypesym ones. It might already work correctly, but better safe than sorry. 😅

That seems to work. Here is my test:

========================================

program gc_ppu;
uses
	gc_types_unit;
var
	a: TShortIntList;
begin
	a.dothis('hello’);
	GlobalShortIntList.dothis('world');
end.

========================================

unit gc_types_unit;
interface
type
	generic TList<T, const U:integer> = record
		const ID = U;
		public
			list: array[0..U-1] of T;
			procedure dothis (msg:string);
	end;
const
	kShortListLength = 128;
type
	TShortIntList = specialize TList<Shortint,kShortListLength>;
var
	GlobalShortIntList: TShortIntList;
implementation
procedure TList.dothis (msg:string);
begin
	writeln('message:',msg, ' high:', high(ID), ' constsize:', sizeof(ID), ' structsize:', sizeof(self));
end;
end.

Regards,
Ryan Joseph

Sven Barth
to Mattias Gaertner: On Tue, 27 Nov 2018 11:35:22 +0100 What about {$WritableConst off} const kSomeDays:TDays…

Am Di., 27. Nov. 2018, 12:03 hat Mattias Gaertner via fpc-pascal <

Show quoted text 21 lines
geschrieben: On Tue, 27 Nov 2018 11:35:22 +0100 Sven Barth via fpc-pascal wrote: >[...] > > const > > kSomeDays:TDays = [Mon, Wed]; >[...] > The difference between typed and untyped constants are available > everywhere else in the language as well, so I have no qualms (at > least for now?) to error out on them. What about {$WritableConst off} const kSomeDays:TDays = [Mon, Wed]; ? BTW, how do you name this? "proper typed const"?

For the compiler it makes no difference whether a typed const is writable or not. A typed const is a typed const and can't be used for some things that untyped consts can be used for.

One further difference: untyped consts might not even be part of the binary's data section in case of ordinal, float or set consts as they simply become part of the code section. Typed consts always reside in the data section.

Regards,
Sven

Sven Barth
to Ryan Joseph: You mean reject them? The default behavior is to get a type_e_type_id_expected error.…

Am Di., 27. Nov. 2018, 14:00 hat Ryan Joseph
geschrieben:

Show quoted text 30 lines
> On Nov 27, 2018, at 5:35 PM, Sven Barth via fpc-pascal < > wrote: > > 1) kSomeDays is a typed const so I get a load node instead of tsetconstnode, which I need to need to know in order to make the name string. See the TODO’s in tgenericparamdef.create. > > type > TDay = (Mon, Tue, Wed); > TDays = set of TDay; > const > kSomeDays:TDays = [Mon, Wed]; > > > var > d: specialize TMyRecord_Set<integer,kSomeDays>; // this doesn’t work > d: specialize TMyRecord_Set<integer,[Mon,Wed]>; // this works because I get a set node > > The difference between typed and untyped constants are available everywhere else in the language as well, so I have no qualms (at least for now?) to error out on them. You mean reject them? The default behavior is to get a type_e_type_id_expected error. They can be included easily but I don’t know how to get the set from the load node (I think it needs further parsing).

We can add a new message that says "type id or untyped constant expected". But if it correctly errors right now, we can leave it be for now.

Show quoted text 65 lines
> > For sets I believe that this should work however (see here: https://freepascal.org/docs-html/current/ref/refse9.html#x21-200002.1 ): > > === code begin === > const > kSomeDays = [Mon, Wed]; > === code end === Yes, sets work because I get a setnode which I can operate on. > > Did you try with the generic and the specialization in different units and changing only one of the two? Though you might want to check whether tstoreddef.ppuload (or wherever tdef.genericparas is initialized after a unit load) correctly instantiated the tconstsym instances instead of ttypesym ones. It might already work correctly, but better safe than sorry. 😅 That seems to work. Here is my test: ======================================== program gc_ppu; uses gc_types_unit; var a: TShortIntList; begin a.dothis('hello’); GlobalShortIntList.dothis('world'); end. ======================================== unit gc_types_unit; interface type generic TList<T, const U:integer> = record const ID = U; public list: array[0..U-1] of T; procedure dothis (msg:string); end; const kShortListLength = 128; type TShortIntList = specialize TList<Shortint,kShortListLength>; var GlobalShortIntList: TShortIntList; implementation procedure TList.dothis (msg:string); begin writeln('message:',msg, ' high:', high(ID), ' constsize:', sizeof(ID), ' structsize:', sizeof(self)); end; end.

You did read the part about the generic and the specialization being located in two different units? 😉

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 27, 2018, at 8:11 PM, Sven Barth via fpc-pascal wrote: We can add a new message that says "type id or untyped constant expected". But if it correctly errors right now, we can leave it be for now.

Fine by me.

You did read the part about the generic and the specialization being located in two different units? 😉

Oops. This still works so I guess it’s ok.

program gc_ppu;
uses
	gc_types_unit;
var
	a: specialize TList<Integer,10>;
begin
	a.dothis('hello');
end.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Di., 27. Nov. 2018, 15:08 hat Ryan Joseph
geschrieben:

Show quoted text 15 lines
> You did read the part about the generic and the specialization being located in two different units? 😉 Oops. This still works so I guess it’s ok. program gc_ppu; uses gc_types_unit; var a: specialize TList<Integer,10>; begin a.dothis('hello'); end.

Does it also work correctly if you change the constant and then recompile? The same if the specialization is inside another unit used by the program?

Regards,
Sven

Ryan Joseph

I just noticed I sent this to the wrong person and the list never saw it so I’m sending it again. I feel like I should try to fix it while I’ve got my eyes on the generics code before I forget.

Is there a reason it’s not implemented yet? In theory you should be able to specialize a function as a type and use the type name as the function name. This is basically the same syntax for class construction but without the .create.

====================

As a side node I haven’t been willing to use generic functions yet because the syntax is so verbose it kind of defeats the purpose.

Why can’t we specialize functions as a type? Maybe that’s on the todo list also?

generic procedure DoThis<T>(msg:string);
begin
	writeln(msg, ' ',sizeof(T));
end;
type
	DoInt = specialize DoThis<integer>;
begin
	DoInt(‘hello');

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph: I just noticed I sent this to the wrong person and the list never saw it so I’m sending…

Am Mi., 28. Nov. 2018, 09:41 hat Ryan Joseph
geschrieben:

Show quoted text 17 lines
I just noticed I sent this to the wrong person and the list never saw it so I’m sending it again. I feel like I should try to fix it while I’ve got my eyes on the generics code before I forget. Is there a reason it’s not implemented yet? In theory you should be able to specialize a function as a type and use the type name as the function name. This is basically the same syntax for class construction but without the .create. ==================== As a side node I haven’t been willing to use generic functions yet because the syntax is so verbose it kind of defeats the purpose. Why can’t we specialize functions as a type? Maybe that’s on the todo list also?

Because that is not supposed to work. Generic routines are *routines*, not types. You can't define aliases for routines either, not to mention the problems with scoping as generic methods exist as well.

Generic routines however have a different boon in Delphi that I plan to add as well: inference of the type parameters based on the parameters the user passed. In that case the type parameter clause as well as the "specialize" in non-Delphi modes would not need to be used and it would look like a normal, non-generic call.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 28, 2018, at 7:26 PM, Sven Barth via fpc-pascal wrote: Because that is not supposed to work. Generic routines are *routines*, not types. You can't define aliases for routines either, not to mention the problems with scoping as generic methods exist as well.

It seems natural you could specialize a generic procedure because you can do this for all other generic types. Maybe a type isn’t the right way but it feels like there should be some way to specialize the procedure header outside of a begin/end block. I know you can't make aliases to procedures but generics are more of “instantiating a template” which falls outside the scope the language in general.

Generic routines however have a different boon in Delphi that I plan to add as well: inference of the type parameters based on the parameters the user passed. In that case the type parameter clause as well as the "specialize" in non-Delphi modes would not need to be used and it would look like a normal, non-generic call.

That’s probably the best way (Swift does this and I always wondered why FPC doesn’t) but it won’t work unless the parameters contain the generic parameter, which is a minority of generics functions anyways.

Just to be sure, you mean like this?

generic procedure DoThis<T>(msg:T);
begin
end;
begin
	DoThis(‘hello’); // compiles as “specialize DoThis<string>(‘hello’)” because param types match generic parameter types

If all that’s involved is comparing params types to generic types then that looks like a relatively simple solution. Const generic params may have complicated this some however.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Mi., 28. Nov. 2018, 15:27 hat Ryan Joseph
geschrieben:

Show quoted text 16 lines
> On Nov 28, 2018, at 7:26 PM, Sven Barth via fpc-pascal < > wrote: > > Because that is not supposed to work. Generic routines are *routines*, not types. You can't define aliases for routines either, not to mention the problems with scoping as generic methods exist as well. It seems natural you could specialize a generic procedure because you can do this for all other generic types. Maybe a type isn’t the right way but it feels like there should be some way to specialize the procedure header outside of a begin/end block. I know you can't make aliases to procedures but generics are more of “instantiating a template” which falls outside the scope the language in general.

You're not wrong, but a type alias is definitely the wrong way. But that is a completely different topic, one that I'm not willing to discuss right now, aside from "we don't support something like that".

Show quoted text 26 lines
> > Generic routines however have a different boon in Delphi that I plan to add as well: inference of the type parameters based on the parameters the user passed. In that case the type parameter clause as well as the "specialize" in non-Delphi modes would not need to be used and it would look like a normal, non-generic call. That’s probably the best way (Swift does this and I always wondered why FPC doesn’t) but it won’t work unless the parameters contain the generic parameter, which is a minority of generics functions anyways. Just to be sure, you mean like this? generic procedure DoThis<T>(msg:T); begin end; begin DoThis(‘hello’); // compiles as “specialize DoThis<string>(‘hello’)” because param types match generic parameter types If all that’s involved is comparing params types to generic types then that looks like a relatively simple solution. Const generic params may have complicated this some however.

Yes, all generic parameters need to be part of the routine's parameters and constant parameters indeed won't work in that case.

Regards,
Sven

Sven Barth
to Sven Barth: Am Di., 27. Nov. 2018, 08:18 hat Ryan Joseph geschrieben: Good, thank you. Hopefully I'll…
Sven Barth wrote 22 lines
Am Di., 27. Nov. 2018, 08:18 hat Ryan Joseph < > geschrieben: > On Nov 27, 2018, at 4:59 AM, Sven Barth via fpc-pascal < > wrote: > > Best check again once you've done the switch to tconstsym for constants. :) > I made most of the changes you mentioned so please look. Including the constsyms didn’t break anything and helped to clean up the code. I would have done that from the start but I wasn’t sure you wanted me mixing in new types. Good, thank you. Hopefully I'll find the time this evening to look at your changes again. :)

Looks better.
The next thing to nuke is the tgenericparamdef. The parameters stored in tdef.genericparas are the parameter symbols, so there should be no need for defs. If necessary some of the code in pgenutil (and related functions) will need to be changed from handling a list of defs to a list of syms.

Regards,
Sven

Ryan Joseph
to Sven Barth
On Nov 29, 2018, at 5:15 AM, Sven Barth via fpc-pascal wrote: Looks better. The next thing to nuke is the tgenericparamdef. The parameters stored in tdef.genericparas are the parameter symbols, so there should be no need for defs. If necessary some of the code in pgenutil (and related functions) will need to be changed from handling a list of defs to a list of syms.

I added that extra type so that it would be compatible with genericdeflist. I don’t see how that can be removed. The const sym is pulled out in generate_specialization_phase2 and renamed (which I forget why already).

So the tgenericparamdef is just a vessel to hold the constsym until generate_specialization_phase2? Either way removing it means breaking genericdeflist right?

if typeparam.nodetype  typen then
  begin
    { the typesym from paramdef will be added to the list in generate_specialization_phase2 }
    paramdef := tgenericparamdef.create(typeparam.resultdef,typeparam,constprettyname);
    genericdeflist.Add(paramdef);
  end
else
  begin
    constprettyname := '';
    genericdeflist.Add(typeparam.resultdef);
  end;

from generate_specialization_phase2:

for i:=0 to genericdef.genericparas.Count-1 do
  begin
    srsym:=tsym(genericdef.genericparas[i]);
    if not (sp_generic_para in srsym.symoptions) then
      internalerror(2013092602);
    // note: ryan
    { set the generic param name of the constsym of tgenericparamdef }
    typedef := tstoreddef(context.genericdeflist[i]);
    if typedef.typ = genericconstdef then
      tgenericparamdef(typedef).typesym.realname := srsym.realname;
    generictypelist.add(srsym.realname,typedef.typesym);
  end;

Regards,
Ryan Joseph

Ryan Joseph
to Sven Barth: Am Mi., 28. Nov. 2018, 15:27 hat Ryan Joseph geschrieben: You're not wrong, but a type…
On Nov 28, 2018, at 11:25 PM, Sven Barth via fpc-pascal wrote: You're not wrong, but a type alias is definitely the wrong way. But that is a completely different topic, one that I'm not willing to discuss right now, aside from "we don't support something like that”.

Ok, maybe something to think about later. Compile time performance could be a reason to pursue down the road.

If all that’s involved is comparing params types to generic types then that looks like a relatively simple solution. Const generic params may have complicated this some however. Yes, all generic parameters need to be part of the routine's parameters and constant parameters indeed won't work in that case.

What are the performance implications of specializing all these types across a code base? I’ve heard c++ developers mention the terrible performance of templates and I think that’s because of the inferred types and inability to specialize once (like FPC does for all other generics except procs).

Looking at this now I think that each unit that uses an inferred specialization will produce a unit procedure for just that unit. Is that right?

Here’s an example which specializes 3 generics (they’re shared right?) across 5 procedure calls.

generic procedure DoThis<T>(msg:T);
begin
end;
generic procedure DoThis<T>(msg:T;param1:T);
begin
end;
generic procedure DoThis<T>(msg:T;param1:integer);
begin
end;
begin
	DoThis('hello'); 	// specialize DoThis<string>('hello');
	DoThis(1); 		// specialize DoThis<integer>(1);
	DoThis('a','b'); 	// specialize DoThis<string>('a','b’);
	DoThis(1,1); 		// specialize DoThis<integer>(1,1);
	DoThis('hello',1); 	// specialize DoThis<string>(‘hello',1);

Regards,
Ryan Joseph

Sven Barth
to denisgolovan

Am Mi., 2. Jan. 2019, 11:20 hat denisgolovan
geschrieben:

Show quoted text 5 lines
Hi, all Could someone confirm this functionality is merged into trunk? I mean constants in generics.

I can confirm that it is not integrated in trunk.

Regards,
Sven

Sven Barth
to Alexander Shishkin

Am Mi., 2. Jan. 2019, 23:41 hat Alexander Shishkin via fpc-pascal <

Show quoted text 21 lines
geschrieben: 06.11.2018 10:13, Ryan Joseph пишет: > > program generic_constants; > > type > generic TList<T, U> = record > list: array[0..U-1] of T; > function capacity: integer; > end; > I`d like to see constant parameter to be constrained with type type generic TList<T; U: Integer> = record list: array[0..U-1] of T; function capacity: integer; end;

That's already been taken care of by Ryan :)

Regards,
Sven

Alexander Shishkin
to Sven Barth

Thanks, now I`ve lost is a discussion again. What is the decision about syntax? Is "const" prefix and/or ": type" suffix required?

generic TList1<T, U> = class
   var data: array[0..U] of T; end;
generic TList2<T; U: Integer> = class
   var data: array[0..U] of T; end;
generic TLis3<T; const U> = class
   var data: array[0..U] of T; end;
generic TList4<T; const U: integer> = class
   var data: array[0..U] of T; end;

03.01.2019 2:15, Sven Barth via fpc-pascal пишет:

Show quoted text 29 lines
Am Mi., 2. Jan. 2019, 23:41 hat Alexander Shishkin via fpc-pascal < > geschrieben: 06.11.2018 10:13, Ryan Joseph пишет: > > program generic_constants; > > type >       generic TList<T, U> = record >               list: array[0..U-1] of T; >               function capacity: integer; >       end; > I`d like to see constant parameter to be constrained with type   type         generic TList<T; U: Integer> = record                 list: array[0..U-1] of T;                 function capacity: integer;         end; That's already been taken care of by Ryan :) Regards, Sven

--AVS

Ryan Joseph
to Alexander Shishkin: 06.11.2018 10:13, Ryan Joseph пишет: I`d like to see constant parameter to be…
Show quoted text 9 lines
On Jan 2, 2019, at 3:13 PM, Alexander Shishkin via fpc-pascal wrote: I`d like to see constant parameter to be constrained with type type generic TList<T; U: Integer> = record list: array[0..U-1] of T; function capacity: integer; end;

This is the syntax we’ve settled on.

type
	generic TList<T, const U: integer> = record
		list: array[0..U-1] of T;
		function capacity: integer;
	end;

Regards,
Ryan Joseph

Ryan Joseph
to Alexander Shishkin: Thanks, now I`ve lost is a discussion again. What is the decision about syntax? Is…
On Jan 2, 2019, at 5:52 PM, Alexander Shishkin via fpc-pascal wrote: Thanks, now I`ve lost is a discussion again. What is the decision about syntax? Is "const" prefix and/or ": type" suffix required?

const is required but the type is not.

Here’s a test for all the const types supported.

program gc_types;
uses
	sysutils;
type
	generic TMyRecord_Int<T, const U:integer> = record
	end;
type
	generic TMyRecord_Byte<T, const U:byte> = record
	end;
type
	generic TMyRecord_String<T, const U:string> = record
	end;
type
	generic TMyRecord_Float<T, const U:single> = record
	end;
type
	TDay = (Mon, Tue, Wed);
	TDays = set of TDay;
	generic TMyRecord_Set<T, const U:TDays> = record
	end;
type
	generic TMyRecord_Nil<T, const U:pointer> = record
	end;
type
	generic TMyRecord_Undefined<T, const U> = record
	end;
var
	a: specialize TMyRecord_Int<integer,10>;
	b: specialize TMyRecord_String<integer,'foo'>;
	c: specialize TMyRecord_Float<integer,0.1>;
	d: specialize TMyRecord_Set<integer,[Mon, Wed]>;
	e: specialize TMyRecord_Nil<integer,nil>;
	f: specialize TMyRecord_Byte<integer,kByte>;
	g: specialize TMyRecord_Undefined<integer,[1,2,3]>;
begin
end.

Regards,
Ryan Joseph

Alexander Shishkin
to Ryan Joseph

03.01.2019 4:29, Ryan Joseph пишет:

Show quoted text 5 lines
type generic TMyRecord_Int<T, const U:integer> = record end;

This is not consistent with constraints. Should be ";" after T.

And what about following examples?

type
  	generic TMyRecord1<const T, const U> = record
  	end;
type
  	generic TMyRecord2<const T; const U> = record
  	end;
type
  	generic TMyRecord3<const T, U> = record
  	end;
type
  	generic TMyRecord4<const T; U> = record
  	end;
Ryan Joseph
to Alexander Shishkin
Show quoted text 6 lines
On Jan 2, 2019, at 8:25 PM, Alexander Shishkin via fpc-pascal wrote: This is not consistent with constraints. Should be ";" after T. And what about following examples?

The semicolon is only needed following a generic parameter which is constrained. This was the normal behavior before constants were introduced.

generic TMyRecord1<T: TObject; const U: integer> = record

Regards,
Ryan Joseph

Alexander Shishkin
to Ryan Joseph

03.01.2019 6:32, Ryan Joseph пишет:

Show quoted text 13 lines
> On Jan 2, 2019, at 8:25 PM, Alexander Shishkin via fpc-pascal wrote: > > This is not consistent with constraints. Should be ";" after T. > > And what about following examples? > The semicolon is only needed following a generic parameter which is constrained. This was the normal behavior before constants were introduced. generic TMyRecord1<T: TObject; const U: integer> = record

It is not exactly true, semicolon separates parameter lists different restrictions including empty one.

This should fail to compile (": integer" restriction can not be applied
to non const parameter):
generic TMyRecord1<T, const U: integer> = record end;
generic TMyRecord1<const T, U: integer> = record end;
This is OK (T is any type):
generic TMyRecord1<T; const U: integer> = record end;
This is OK (both T and U are integer):
generic TMyRecord1<const T, const U: integer> = record end;
This is OK (T is any const,  U and V are integer)
generic TMyRecord1<const T; const U, const V: integer> = record end;

--AVS

Mattias Gaertner
to Alexander Shishkin

On Thu, 3 Jan 2019 14:58:00 +0300
Alexander Shishkin via fpc-pascal

[...] This is OK (both T and U are integer): generic TMyRecord1<const T, const U: integer> = record end;

That is inconsistent to normal Pascal arguments.

<accessors> Name[, Name ...][:type]

Isn't it?

Mattias

Sven Barth
to Alexander Shishkin: 03.01.2019 6:32, Ryan Joseph пишет: It is not exactly true, semicolon separates…

Am Do., 3. Jan. 2019, 12:58 hat Alexander Shishkin via fpc-pascal <

Show quoted text 36 lines
geschrieben: 03.01.2019 6:32, Ryan Joseph пишет: > > >> On Jan 2, 2019, at 8:25 PM, Alexander Shishkin via fpc-pascal < > wrote: >> >> This is not consistent with constraints. Should be ";" after T. >> >> And what about following examples? >> > > The semicolon is only needed following a generic parameter which is constrained. This was the normal behavior before constants were introduced. > > generic TMyRecord1<T: TObject; const U: integer> = record > It is not exactly true, semicolon separates parameter lists different restrictions including empty one. This should fail to compile (": integer" restriction can not be applied to non const parameter): generic TMyRecord1<T, const U: integer> = record end; generic TMyRecord1<const T, U: integer> = record end; This is OK (T is any type): generic TMyRecord1<T; const U: integer> = record end; This is OK (both T and U are integer): generic TMyRecord1<const T, const U: integer> = record end; This is OK (T is any const, U and V are integer) generic TMyRecord1<const T; const U, const V: integer> = record end;

Correct. ";" separates different parameter types, "," separates those of the same type. Though I agree with Mattias that "const T, const U" (with or without ": Integer") should not work (but then "T, const U" should also not work for consistency).
Essentially it should behave like a routine's parameter clause.

Regards,
Sven

Alexander Shishkin
to Mattias Gaertner: On Thu, 3 Jan 2019 14:58:00 +0300 Alexander Shishkin via fpc-pascal That is inconsistent…

03.01.2019 15:45, Mattias Gaertner via fpc-pascal пишет:

Show quoted text 14 lines
On Thu, 3 Jan 2019 14:58:00 +0300 Alexander Shishkin via fpc-pascal wrote: > [...] > This is OK (both T and U are integer): > generic TMyRecord1<const T, const U: integer> = record end; That is inconsistent to normal Pascal arguments. <accessors> Name[, Name ...][:type] Isn't it?

I personally do not like const prefix either. More consistent with other constraints would be:

T and U integer const
generic TMyRecord1<T, U: integer> = record end;
generic TMyRecord1<T, U: const, integer> = record end;
Similar to
generic TMyRecord1<T, U: IUnknown> = record end;
generic TMyRecord1<T, U: class, IUnknown> = record end;
T and U any const
generic TMyRecord1<T, U: const> = record end;
Similar to
generic TMyRecord1<T, U: record> = record end;

And more complex example:

generic TMyRecord1<T, U: record; X, Y: const> = record end;
Sven Barth
to Alexander Shishkin

Am Do., 3. Jan. 2019, 14:24 hat Alexander Shishkin via fpc-pascal <

Show quoted text 39 lines
geschrieben: 03.01.2019 15:45, Mattias Gaertner via fpc-pascal пишет: > On Thu, 3 Jan 2019 14:58:00 +0300 > Alexander Shishkin via fpc-pascal > wrote: > >> [...] >> This is OK (both T and U are integer): >> generic TMyRecord1<const T, const U: integer> = record end; > > That is inconsistent to normal Pascal arguments. > > <accessors> Name[, Name ...][:type] > > Isn't it? > I personally do not like const prefix either. More consistent with other constraints would be: T and U integer const generic TMyRecord1<T, U: integer> = record end; generic TMyRecord1<T, U: const, integer> = record end; Similar to generic TMyRecord1<T, U: IUnknown> = record end; generic TMyRecord1<T, U: class, IUnknown> = record end; T and U any const generic TMyRecord1<T, U: const> = record end; Similar to generic TMyRecord1<T, U: record> = record end; And more complex example: generic TMyRecord1<T, U: record; X, Y: const> = record end;

I'm against that. A constant parameter is a different beast from a type parameter so requiring a special prefix for them is legitimate. Also this is more like the normal routine parameter syntax, thus everyone will feel ,
Sven

Ryan Joseph
to Sven Barth: Am Do., 3. Jan. 2019, 12:58 hat Alexander Shishkin via fpc-pascal < Correct. ";"…
On Jan 3, 2019, at 6:20 AM, Sven Barth via fpc-pascal wrote: Correct. ";" separates different parameter types, "," separates those of the same type. Though I agree with Mattias that "const T, const U" (with or without ": Integer") should not work (but then "T, const U" should also not work for consistency). Essentially it should behave like a routine's parameter clause.

I guess that makes sense that if a type parameter is followed by a const parameter then those are essentially different types and following the same logic should be separated with a semicolon.

That means the parser only knows if a ; is required after the const keyword is found. Does that matter?

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Do., 3. Jan. 2019, 17:50 hat Ryan Joseph
geschrieben:

Show quoted text 18 lines
> On Jan 3, 2019, at 6:20 AM, Sven Barth via fpc-pascal < > wrote: > > Correct. ";" separates different parameter types, "," separates those of the same type. Though I agree with Mattias that "const T, const U" (with or without ": Integer") should not work (but then "T, const U" should also not work for consistency). > Essentially it should behave like a routine's parameter clause. I guess that makes sense that if a type parameter is followed by a const parameter then those are essentially different types and following the same logic should be separated with a semicolon. That means the parser only knows if a ; is required after the const keyword is found. Does that matter?

The approach should be like this:
- const is allowed at the start of the parameters or after a ";"
- a parameter name is either followed by a ",", ";", ":" or ">" (that should already be the case)
- a ":" is either followed by a type constraint (for non const parameters) or a type name that allows constants (for const parameters); in either case this is followed by a ";" or the closing ">"

I think that should cover everything 🤔

Regards,
Sven

Ryan Joseph
to Sven Barth
Show quoted text 9 lines
On Jan 3, 2019, at 11:33 AM, Sven Barth via fpc-pascal wrote: The approach should be like this: - const is allowed at the start of the parameters or after a ";" - a parameter name is either followed by a ",", ";", ":" or ">" (that should already be the case) - a ":" is either followed by a type constraint (for non const parameters) or a type name that allows constants (for const parameters); in either case this is followed by a ";" or the closing ">" I think that should cover everything 🤔

Ok, I’ll try to change that later since it’s minor.

I read your old email about removing “tgenericparamdef”: quote from you: "Looks better. The next thing to nuke is the tgenericparamdef. The parameters stored in tdef.genericparas are the parameter symbols, so there should be no need for defs. If necessary some of the code in pgenutil (and related functions) will need to be changed from handling a list of defs to a list of syms.”

You’re probably right that’s better design but that means I need to overhaul a rather fundamental element of how generics were designed and goes well outside the changes I made. At the core of the problem is tspecializationcontext.genericdeflist contains tdef’s and changing that to symbols means I need to rename lots of variables, move methods from tstoreddef and all the other various places where types were used. Sorry but there’s just not enough time for me to dive into that right now so that will have to wait.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph
Ryan Joseph wrote 16 lines
> On Jan 3, 2019, at 11:33 AM, Sven Barth via fpc-pascal wrote: > > The approach should be like this: > - const is allowed at the start of the parameters or after a ";" > - a parameter name is either followed by a ",", ";", ":" or ">" (that should already be the case) > - a ":" is either followed by a type constraint (for non const parameters) or a type name that allows constants (for const parameters); in either case this is followed by a ";" or the closing ">" > > I think that should cover everything 🤔 > Ok, I’ll try to change that later since it’s minor. I read your old email about removing “tgenericparamdef”: quote from you: "Looks better. The next thing to nuke is the tgenericparamdef. The parameters stored in tdef.genericparas are the parameter symbols, so there should be no need for defs. If necessary some of the code in pgenutil (and related functions) will need to be changed from handling a list of defs to a list of syms.” You’re probably right that’s better design but that means I need to overhaul a rather fundamental element of how generics were designed and goes well outside the changes I made. At the core of the problem is tspecializationcontext.genericdeflist contains tdef’s and changing that to symbols means I need to rename lots of variables, move methods from tstoreddef and all the other various places where types were used. Sorry but there’s just not enough time for me to dive into that right now so that will have to wait.

Fair enough. In that case support for constants in generics will have to wait as well. 🤷‍

Regards,
Sven

Ryan Joseph
to Sven Barth
On Jan 3, 2019, at 11:11 PM, Sven Barth via fpc-pascal wrote: Fair enough. In that case support for constants in generics will have to wait as well. 🤷‍

Who designed the generics btw? I’m not 100% certain that it’s even safe to change their design like this so it would be nice to hear from them. Inline specializations rely on tprocdef and if I change everything to work on symbols we could break generic procedure overloads.

One thing I can do to remove my dummy tgenericparamdef class is to keep a separate list of const syms in tspecializationcontext.

Regards,
Ryan Joseph

Unknown author
to Ryan Joseph
Ryan Joseph wrote 6 lines
> On Jan 3, 2019, at 11:11 PM, Sven Barth via fpc-pascal wrote: > > Fair enough. In that case support for constants in generics will have to wait as well. 🤷‍ Who designed the generics btw?

unless i'm highly mistaken, you've been talking to them... they're quoted above, even ;)

NOTE: No off-list assistance is given without prior approval. *Please keep mailing list traffic on the list unless* *a signed and pre-paid contract is in effect with us.*
Sven Barth
to Unknown author

Am Fr., 4. Jan. 2019, 21:11 hat geschrieben:

Show quoted text 15 lines
On 1/4/19 1:47 PM, Ryan Joseph wrote: > >> On Jan 3, 2019, at 11:11 PM, Sven Barth via fpc-pascal < > wrote: >> >> Fair enough. In that case support for constants in generics will have to wait as well. 🤷‍ > > Who designed the generics btw? unless i'm highly mistaken, you've been talking to them... they're quoted above, even ;)

I didn't design the original implementation, but the improvements for the last five years or so have been mine. 😅

@Ryan: I can give it a try to rework the list from defs to syms and you can then put your const implementation on top of that.

Regards,
Sven

Unknown author
to Sven Barth
Sven Barth wrote 9 lines
Am Fr., 4. Jan. 2019, 21:11 hat geschrieben: On 1/4/19 1:47 PM, Ryan Joseph wrote: > Who designed the generics btw? unless i'm highly mistaken, you've been talking to them... they're quoted above, even ;) I didn't design the original implementation, but the improvements for the last five years or so have been mine. 😅

i didn't pick up this list until 2011 Feb 08... i don't recall when i first read anything about generics and FPC but it was a while after i had joined... your voice was the only one responding to questions and producing the code...

@Ryan: I can give it a try to rework the list from defs to syms and you can then put your const implementation on top of that.

that would be nice... const(ants?) have been asked for a lot in generics for FPC...

NOTE: No off-list assistance is given without prior approval. *Please keep mailing list traffic on the list unless* *a signed and pre-paid contract is in effect with us.*
Ryan Joseph
to Sven Barth: Am Fr., 4. Jan. 2019, 21:11 hat geschrieben: I didn't design the original…
Show quoted text 6 lines
On Jan 5, 2019, at 1:18 AM, Sven Barth via fpc-pascal wrote: I didn't design the original implementation, but the improvements for the last five years or so have been mine. 😅 @Ryan: I can give it a try to rework the list from defs to syms and you can then put your const implementation on top of that.

Last night I just went for it and pulled out the defs which was smoother than I thought. It seems to work out but I need to test more I think.

Regards,
Ryan Joseph

Ryan Joseph
to Ryan Joseph

Given the new syntax requirements we talked about there needs to be a ; after T now. What error should be used if there isn’t a ; semicolon separator between types and consts? I just used a parser_e_illegal_expression for now.

generic TStuff<T,const A,B,C:integer> = record

Regards,
Ryan Joseph

Jonas Maebe
to Ryan Joseph
Ryan Joseph wrote
Given the new syntax requirements we talked about there needs to be a ; after T now. What error should be used if there isn’t a ; semicolon separator between types and consts? I just used a parser_e_illegal_expression for now.

scan_f_syn_expected=02003_F_Syntax error, "$1" expected but "$2" found

Jonas

Sven Barth
to Ryan Joseph: Last night I just went for it and pulled out the defs which was smoother than I thought.…

Am Sa., 5. Jan. 2019, 17:28 hat Ryan Joseph
geschrieben:

Show quoted text 15 lines
> On Jan 5, 2019, at 1:18 AM, Sven Barth via fpc-pascal < > wrote: > > I didn't design the original implementation, but the improvements for the last five years or so have been mine. 😅 > > @Ryan: I can give it a try to rework the list from defs to syms and you can then put your const implementation on top of that. > Last night I just went for it and pulled out the defs which was smoother than I thought. It seems to work out but I need to test more I think.

Sometimes things *do* work out :D

Regards,
Sven

Ryan Joseph
to Jonas Maebe: scan_f_syn_expected=02003_F_Syntax error, "$1" expected but "$2" found Jonas

Here’s the new syntax requirements for const params which require types and consts be separated by semicolons.

Does TStuff2 need a ; between the two const params? Sven said consts must follow ; or < but they’re both consts so I thought I’d ask to make sure.

type
	generic TStuff0<A:TObject;T> = record end;
	generic TStuff1<T;const A,B,C:integer> = record end;
	generic TStuff2<const A,const B,C:string> = record end;
	generic TStuff3<const A:integer;T> = record end;

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Sa., 5. Jan. 2019, 22:57 hat Ryan Joseph
geschrieben:

Show quoted text 12 lines
Here’s the new syntax requirements for const params which require types and consts be separated by semicolons. Does TStuff2 need a ; between the two const params? Sven said consts must follow ; or < but they’re both consts so I thought I’d ask to make sure. type generic TStuff0<A:TObject;T> = record end; generic TStuff1<T;const A,B,C:integer> = record end; generic TStuff2<const A,const B,C:string> = record end; generic TStuff3<const A:integer;T> = record end;

Just think about it logically: B and C can't be anything else than const, so why would we need to repeat it?

Regards,
Sven

Ryan Joseph
to Sven Barth
On Jan 6, 2019, at 1:28 AM, Sven Barth via fpc-pascal wrote: Just think about it logically: B and C can't be anything else than const, so why would we need to repeat it?

I’m not saying anyone would actually write that generic but if it does appear what terminator is used? A more clear example is: <const A,const B>. It would make more sense to say <const A,B> of course but it could appear the other way. Maybe I’m not understand you though.

For now I just required all const lists separated with ; since that was your first suggestions, i.e. <const A;const B>

Regards,
Ryan Joseph

Ryan Joseph
to Ryan Joseph

Last question I have I think.

Is this the best way to get "pretty name" string for a set? Having to loop through the entire range of the set is not so great but I don’t see another way. I feel like there should be some utility functions to get names for ord defs also but I just did a crude string conversion for now.

new(ps);
ps^:=tsetconstnode(node).value_set^;
sym:=cconstsym.create_ptr(undefinedname,constset,ps,fromdef);
setdef:=tsetdef(tsetconstnode(node).resultdef);
enumdef:=tenumdef(setdef.elementdef);
prettyname:='[';
for i := setdef.setbase to setdef.setmax do
  if i in tsetconstnode(node).value_set^ then
    begin
      enumsym:=enumdef.int2enumsym(i);
      if assigned(enumsym) then
        enumname:=enumsym.realname
      else if enumdef.typ=orddef then
        begin
          if torddef(enumdef).ordtype=uchar then
            enumname:=chr(i)
          else
            enumname:=tostr(i);
        end
      else
        enumname:=tostr(i);
      if length(prettyname) > 1 then
        prettyname:=prettyname+','+enumname
      else
        prettyname:=prettyname+enumname;
    end;
prettyname:=prettyname+']';

Regards,
Ryan Joseph

Ben Grasset
to Ryan Joseph

Just wanted to say, I've been playing around with Ryan's github branch that contains this functionality and I'm loving it! It really opens up a ton of interesting possibilities in general, and also a lot of potential for optimization via constant folding for generic containers that wasn't really possible previously. Here's a more "advanced" example I threw together of the kinds of things you can actually do with this (I'm aware the final syntax for the declarations is going to be slightly different, not that it matters really):

program Example;
{$Mode ObjFPC}{$H+}{$J-}
{$ModeSwitch AdvancedRecords}
{$PointerMath On}
type
  generic TStaticList<T, const L: PtrUInt, const H: PtrUInt> = record
  public type
    TStaticListEnumerator = record
    public type PT = ^T;
    strict private
      FFirst, FLast, FCurrent: PT;
    public
      class function Create(var List: TStaticList): TStaticListEnumerator;
inline; static;
      function MoveNext: Boolean; inline;
      property Current: PT read FCurrent;
    end;
  strict private
    Items: array[L..H] of T;
  public
    function Low: PtrUInt; inline;
    function High: PtrUInt; inline;
    procedure Initialize; inline;
    function GetEnumerator: TStaticListEnumerator; inline;
  end;
  class function TStaticList.TStaticListEnumerator.Create(var List:
TStaticList): TStaticListEnumerator;
  begin
    with Result do begin
      FFirst := @List.Items[L];
      FLast := @List.Items[H];
      FCurrent := FFirst - 1;
    end;
  end;
  function TStaticList.TStaticListEnumerator.MoveNext: Boolean;
  begin
    Inc(FCurrent);
    Result := (FFirst  FLast) and (FCurrent <= FLast);
  end;
  function TStaticList.Low: PtrUInt;
  begin
    Result := L;
  end;
  function TStaticList.High: PtrUInt;
  begin
    Result := H;
  end;
  procedure TStaticList.Initialize;
  var I: PtrUInt;
  begin
    for I := Low() to High() do Items[I] := T(I);
  end;
  function TStaticList.GetEnumerator: TStaticListEnumerator;
  begin
    Result := TStaticListEnumerator.Create(Self);
  end;
var
  AsciiList: specialize TStaticList<Char, 33, 126>;
  P: PChar;
begin
  AsciiList.Initialize();
  for P in AsciiList do Write(P^);
end.
Ryan Joseph
to Ben Grasset
On Jan 6, 2019, at 1:31 PM, Ben Grasset wrote: generic TStaticList<T, const L: PtrUInt, const H: PtrUInt> = record

This is exactly the reason I added the feature, there’s no way to extend static arrays otherwise! Beyond this one thing I have no idea what to use them for. ;)

Regards,
Ryan Joseph

Ben Grasset
to Ryan Joseph
PM Ryan Joseph wrote
This is exactly the reason I added the feature, there’s no way to extend static arrays otherwise! Beyond this one thing I have no idea what to use them for. ;)

I've got a bunch of stuff in mind actually that I'm waiting on the "final" version for before I start getting into too seriously. There's a lot that's possible as far as custom string types and such if you combine this with operator overloading.

Sven Barth
to Ryan Joseph: Last question I have I think. Is this the best way to get "pretty name" string for a set?…

Am So., 6. Jan. 2019, 19:07 hat Ryan Joseph
geschrieben:

Show quoted text 7 lines
Last question I have I think. Is this the best way to get "pretty name" string for a set? Having to loop through the entire range of the set is not so great but I don’t see another way. I feel like there should be some utility functions to get names for ord defs also but I just did a crude string conversion for now.

I don't think that there is a better way. Also up to now it wasn't necessary for the compiler to do this, thus there are no utility functions for it.

Regards,
Sven

Sven Barth
to Ryan Joseph: Since we introduced “const” keywords to generic params does it make sense to use an…

Am So., 6. Jan. 2019, 22:38 hat Ryan Joseph
geschrieben:

Show quoted text 7 lines
Since we introduced “const” keywords to generic params does it make sense to use an optional “type” keyword also? That just occurred to me as something worth discussing. No opinion either way except there’s a inconsistency now which may bother some people. generic TStuff<type T; const A> = record end;

We can always add it in later if we think it's needed. Let's keep it out for now.

Regards,
Sven

Ben Grasset
to Ryan Joseph

You still have two C-style operator += instances in pgenutil.pas FYI, that don't compile with the default settings.

PM Ryan Joseph wrote 11 lines
I updated the github with the requested changes. Is that everything? I’ll submit a patch if so. https://github.com/genericptr/freepascal/tree/generic_constants Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Ben Grasset
to Ryan Joseph: I updated the github with the requested changes. Is that everything? I’ll submit a…

Also, there's:

pgenutil.pas(158,28) Warning: Class types "tenumdef" and "torddef" are not
related

which breaks compilation because the compiler is built with -sew turned on if you do it through the normal makefiles. I think you need tests before you do a patch, also? (Unless you already have them ready somewhere and just didn't upload them yet.)

PM Ryan Joseph wrote 11 lines
I updated the github with the requested changes. Is that everything? I’ll submit a patch if so. https://github.com/genericptr/freepascal/tree/generic_constants Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Ryan Joseph
to Ben Grasset
Show quoted text 7 lines
On Jan 6, 2019, at 11:32 PM, Ben Grasset wrote: Also, there's: pgenutil.pas(158,28) Warning: Class types "tenumdef" and "torddef" are not related which breaks compilation because the compiler is built with -sew turned on if you do it through the normal makefiles. I think you need tests before you do a patch, also? (Unless you already have them ready somewhere and just didn't upload them yet.)

Thanks, I got these fixes and updated git.

I found some bugs with booleans so I need to fix those also. I’ve got a few tests I’ll post next to make sure they cover everything.

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph: I updated the github with the requested changes. Is that everything? I’ll submit a…
Ryan Joseph wrote
I updated the github with the requested changes. Is that everything? I’ll submit a patch if so. https://github.com/genericptr/freepascal/tree/generic_constants

Looks better.

The following points remain:
- make sure that you don't have any unrelated changes (just look at all the completely unrelated changes in this commit
https://github.com/genericptr/freepascal/commit/bd76667453914106a51d31672d841b1344066e6a )
- make sure that the code you added follows the compiler's formatting (there are quite some locations where you have spaces between identifiers and operators)
And most importantly:
- add tests: both for successful compilations as well as ones that should fail (though everything that produces an error needs to be its own test); I suggest tests/test/tgenconst*.pp as a name (for more infos you can look at http://wiki.freepascal.org/Testing_FPC )

You should also make sure that your changes don't break anything, so you should run the testsuite once without your changes, save tests/output/<cpu>-<os>/{faillog,log,longlog} and then run the testsuite again with your changes and compare especially faillog (see http://wiki.freepascal.org/Testing_FPC#Recommended_Workflow ).

Regards,
Sven

Ryan Joseph
to Sven Barth

Yesterday I discovered I wasn’t doing proper range checking for ordinal constants so for example, a generic with “const U: byte” could be specialized as <1000>. We want ordinal constants to respect range checks right?

I made a little utility function which works in most cases but I don’t think it’s correct and more importantly this function probably already exists in the compiler.

    function compare_orddef_by_range(param1,param2:torddef): boolean;
      var
        orddef1,orddef2:tdef;
      begin
         range_to_type(param1.low,param1.high,orddef1);
         range_to_type(param2.low,param2.high,orddef2);
         result:=compare_defs(orddef1,orddef2,nothingn)>=te_convert_l1;
      end;

Is there an existing function for this or a better way to do it?

Regards,
Ryan Joseph

Jonas Maebe
to Ryan Joseph
Ryan Joseph wrote 13 lines
I made a little utility function which works in most cases but I don’t think it’s correct and more importantly this function probably already exists in the compiler. function compare_orddef_by_range(param1,param2:torddef): boolean; var orddef1,orddef2:tdef; begin range_to_type(param1.low,param1.high,orddef1); range_to_type(param2.low,param2.high,orddef2); result:=compare_defs(orddef1,orddef2,nothingn)>=te_convert_l1; end; Is there an existing function for this or a better way to do it?

defutils.testrange

Jonas

Ryan Joseph
to Jonas Maebe

I’ve made constants respect their proper definition now so we can do range checking but this broke some other things. For example there’s a range check error with static arrays now because “U” has a value of 0 so 0-1 = -1:

type
	generic TList<const U:byte> = record
		list: array[0..U-1] of integer; // ERROR: high value < low value (0..-1)
	end;

There’s a couple ways to resolve this:

1) Add some extra flags to nodes/syms so “U-1” can be known to contain a generic parameter and errors can be ignored. That’s the most complicated solution and I wasn’t able to figure that out easily because flags need to be transferred in multiple locations.

2) The easy solution is to add another tdef member to tconstsym which can be used for range checking in generics and let the tconstsym.typedef def be undefined like it used to be (which fixes the above bug and perhaps others). This means another def needs to be added to the PPU write/read system though.

3) Be lazy and don’t do range checking. :)

Any idea about this?

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Di., 8. Jan. 2019, 21:01 hat Ryan Joseph
geschrieben:

Show quoted text 18 lines
I’ve made constants respect their proper definition now so we can do range checking but this broke some other things. For example there’s a range check error with static arrays now because “U” has a value of 0 so 0-1 = -1: type generic TList<const U:byte> = record list: array[0..U-1] of integer; // ERROR: high value < low value (0..-1) end; There’s a couple ways to resolve this: 1) Add some extra flags to nodes/syms so “U-1” can be known to contain a generic parameter and errors can be ignored. That’s the most complicated solution and I wasn’t able to figure that out easily because flags need to be transferred in multiple locations.

This is the preferred solution as otherwise you'd also defeat the purpose of type safety and probably open another can of worms when constant parameters are used in more complex expressions.

The symbols should already have sp_generic_param set (if not that should definitely be changed so that it matches the type symbols). Now you only need a way to differentiate whether the symbol is an undefined generic parameter or not.
For type parameters it abuses the fact that the undefined defs of the parameters are children of the generic while the final types are never children of the generic. Maybe we can use something similar for the constants?
If not we can always add some flag to tconstsym...

Regards,
Sven

Benito van der Zander
to Sven Barth

Hi,

that reminds me of https://bugs.freepascal.org/view.php?id=34232

Bye,
Benito

Sven Barth wrote 44 lines
Am Di., 8. Jan. 2019, 21:01 hat Ryan Joseph < > geschrieben: I’ve made constants respect their proper definition now so we can do range checking but this broke some other things. For example there’s a range check error with static arrays now because “U” has a value of 0 so 0-1 = -1: type         generic TList<const U:byte> = record                 list: array[0..U-1] of integer; // ERROR: high value < low value (0..-1)         end; There’s a couple ways to resolve this: 1) Add some extra flags to nodes/syms so “U-1” can be known to contain a generic parameter and errors can be ignored. That’s the most complicated solution and I wasn’t able to figure that out easily because flags need to be transferred in multiple locations. This is the preferred solution as otherwise you'd also defeat the purpose of type safety and probably open another can of worms when constant parameters are used in more complex expressions. The symbols should already have sp_generic_param set (if not that should definitely be changed so that it matches the type symbols). Now you only need a way to differentiate whether the symbol is an undefined generic parameter or not. For type parameters it abuses the fact that the undefined defs of the parameters are children of the generic while the final types are never children of the generic. Maybe we can use something similar for the constants? If not we can always add some flag to tconstsym... Regards, Sven _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Ryan Joseph
to Benito van der Zander: that reminds me of https://bugs.freepascal.org/view.php?id=34232 Benito
On Jan 8, 2019, at 3:49 PM, Benito van der Zander wrote: that reminds me of https://bugs.freepascal.org/view.php?id=34232

In this case maybe generics should just not give range errors unless they’re specialized? This bug and the problem I have are only for unspecialized generics. That would be the easiest and laziest thing to do but it would mean this would compile:

type
	generic TList<T> = record
		list: array[0..0-1] of integer;
	end;

Regards,
Ryan Joseph

Sven Barth
to Ryan Joseph

Am Mi., 9. Jan. 2019, 17:03 hat Ryan Joseph
geschrieben:

Show quoted text 17 lines
> On Jan 8, 2019, at 3:49 PM, Benito van der Zander wrote: > > that reminds me of https://bugs.freepascal.org/view.php?id=34232 In this case maybe generics should just not give range errors unless they’re specialized? This bug and the problem I have are only for unspecialized generics. That would be the easiest and laziest thing to do but it would mean this would compile: type generic TList<T> = record list: array[0..0-1] of integer; end;

Which is why I said that this is not the way to go. You need to disable range checks only for those symbols that must not use it. Maybe add a "undefined" flag to tconstsym which is set in your tconstsym.create_undefined and not changed when the const type is changed.

Regards,
Sven