Recent

Author Topic: Unexpected FPC v3.0.4 Behavior  (Read 2356 times)

TRon

  • Hero Member
  • *****
  • Posts: 536
Re: Unexpected FPC v3.0.4 Behavior
« Reply #15 on: June 27, 2020, 11:37:05 am »
... it most definitely should not since it should not see ProcedureName as an array type.
Isn't that a matter of interpretation ?

I looked at it from the point: last defined, first served (or overruled if you so like since i can't seem to address the procedure itself whatsoever). Then it seems pretty logical ?
No, because when the compiler searches for the definition of ParameterName in the scopes stack, it's supposed to find the definition of ParameterName as a procedure first since the definition as an array is further above it and the scope for ProcedureName _must_ already be defined when it starts parsing the procedure parameters because they must belong to the ProcedureName procedure scope.
I am not sure i understood correctly what you wrote there, but i believe you are forgetting there that the parameter of that procedure is defined last in that scope.

Thereby, It seems the compiler has a mind of its own sometimes  :)

I've experimented a little, because i refuse to believe that i can't be able to 'reach' the procedure. In doing so there where some nice pointers from the compiler that seem to suggest different as what you wrote.

I finally was able to come up with this declaration inside the nested proc.
Code: Pascal  [Select][+][-]
  1.     var
  2.      AnotherName : procedure(P: ProcedureName) absolute ProcedureName;
  3.  
As such you seem then able to differentiate between all three.

440bx

  • Hero Member
  • *****
  • Posts: 2017
Re: Unexpected FPC v3.0.4 Behavior
« Reply #16 on: June 27, 2020, 12:04:53 pm »
I am not sure i understood correctly what you wrote there, but i believe you are forgetting there that the parameter of that procedure is defined last in that scope.
I'll try to explain in a hopefully easier to see way.  The scope stack when "ParameterName" is used as ParameterA's data type looks like this:
Code: Text  [Select][+][-]
  1. Scope stack
  2.  
  3.   Standard block
  4.     TestProcedureName                        { new scope }
  5.       ProcedureName         { is array     }
  6.       ProcedureA                             { new scope }
  7.         ProcedureName       { is procedure } { new scope }
  8.           ParameterA
  9.  
Given that stack (which is searched bottom up):

in (ParameterA : ProcedureName), ProcedureName should be seen as a procedure not as an array because the array definition is masked by the later definition of a procedure with the same name as the array type (ProcedureName)


Thereby, It seems the compiler has a mind of its own sometimes  :)
That, it does. :)

Quote
I've experimented a little, because i refuse to believe that i can't be able to 'reach' the procedure. In doing so there where some nice pointers from the compiler that seem to suggest different as what you wrote.
Even if there is a way to "reach" the procedure, I wouldn't trust it because that code shouldn't compile.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

TRon

  • Hero Member
  • *****
  • Posts: 536
Re: Unexpected FPC v3.0.4 Behavior
« Reply #17 on: June 27, 2020, 12:08:51 pm »
Let me address the (for me) simplest first, i need some time to comprehend your elaborations (for which i thank you).

Even if there is a way to "reach" the procedure, I wouldn't trust it because that code shouldn't compile.
i don't quite see what can be wrong with:
Code: Pascal  [Select][+][-]
  1. { This compiles with fpc 3.2.0  }
  2.  
  3. {$MODE OBJFPC}
  4.  
  5. {$DEFINE CALL_A}
  6. {$DEFINE CALL_B}
  7. {$DEFINE CALL_C}
  8.  
  9. program try2confusefpc4;
  10.  
  11.   type
  12.     ProcedureName = array[1..2] of integer;
  13.  
  14.   procedure ProcedureA;
  15.  
  16.     procedure ProcedureName(ProcedureName : ProcedureName);
  17.     var
  18.       AnotherName : procedure(P: ProcedureName) absolute ProcedureName;
  19.       SomeData    : ProcedureName = (1,2);
  20.     begin
  21.       { use local proc parameter ProcedureName }
  22.  
  23.       {$IFDEF CALL_A} ProcedureName[1] := 12;                {$ENDIF}
  24.       {$IFDEF CALL_A} SomeData := ProcedureName;             {$ENDIF}
  25.       {$IFDEF CALL_A} SomeData[1] := ProcedureName[1];       {$ENDIF}
  26.       {$IFDEF CALL_A} SomeData[2] := ProcedureName[2];       {$ENDIF}
  27.  
  28.       { use the nested proc ProcedureName, and the local proc parameter ProcedureName }
  29.  
  30.       {$IFDEF CALL_B} AnotherName(ProcedureName);     {$ENDIF}
  31.       {$IFDEF CALL_B} AnotherName(SomeData);          {$ENDIF}
  32.     end;
  33.  
  34.   begin
  35.   end;
  36.  
  37. begin
  38. end.
  39.  

TRon

  • Hero Member
  • *****
  • Posts: 536
Re: Unexpected FPC v3.0.4 Behavior
« Reply #18 on: June 27, 2020, 12:41:34 pm »
I'll try to explain in a hopefully easier to see way.  The scope stack when "ParameterName" is used as ParameterA's data type looks like this:
Code: Text  [Select][+][-]
  1. Scope stack
  2.  
  3.   Standard block
  4.     TestProcedureName                        { new scope }
  5.       ProcedureName         { is array     }
  6.       ProcedureA                             { new scope }
  7.         ProcedureName       { is procedure } { new scope }
  8.           ParameterA
  9.  
Given that stack (which is searched bottom up):
Ok, that is what i was doing as well. For me meaning that parameterA is last in scope (or as i wrote, overrules the name of the procedure, by lack of better/precise/academic naming).

Quote
in (ParameterA : ProcedureName), ProcedureName should be seen as a procedure not as an array because the array definition is masked by the later definition of a procedure with the same name as the array type (ProcedureName)
That is where we seem to have a difference in interpretation of what the compiler actually does. Tbh, I have no idea what the compiler actually should do in this kind of situation.

It is not to mock your example(s) or question(s) but I try to bring things back to basic and as the compiler tells me to (eg workaround the issue, in case there is any). For me that also means: garbage in, garbage out.

The reuse (and imho 'misuse') of the same names meaning different things is an accident waiting to happen. That is for example why we all agreed to use t-prefix for type declarations, and for sure you are allowed to ignore that agreement. But isn't that what leads up to these kind of confusions in the first place ?

Thereby, It seems the compiler has a mind of its own sometimes  :)
That, it does. :)
I agree that it sometimes is confusing. At least it sometimes confuses the hell out of me. But ... I usually do not try to come up with such kind of examples as you just did  :D

Quote
Even if there is a way to "reach" the procedure, I wouldn't trust it because that code shouldn't compile.
I can agree to that to a certain point. If i go with the "garbage in, garbage out" then I wouldn't expect/rely on anything that comes out of the snippet i posted.

If you bring things back to basics then the code usually becomes more clear and as such easier to be interpreted.

If you truly have the opinion this code should not lead to anything useful that can be relied on, then it would perhaps be better to take it up with one of the developers ? The snippet seems logical enough to me (even though the actual code doesn't make any sense) but that doesn't have to mean that it actually is  :)

edit ps: I am more confused about the fact that the compiler seems to use another approach for the variable declaration block then it does for the actual code.
« Last Edit: June 27, 2020, 12:50:43 pm by TRon »

TRon

  • Hero Member
  • *****
  • Posts: 536
Re: Unexpected FPC v3.0.4 Behavior
« Reply #19 on: June 27, 2020, 01:09:58 pm »
Even if there is a way to "reach" the procedure, I wouldn't trust it because that code shouldn't compile.
Ok, i see what you mean now  :)

Sorry for not catching that sooner. Compiler tells something different at compile-time then the code actually seems to do at runtime.
Code: [Select]
Runtime error 214 at $00000022]

PascalDragon

  • Hero Member
  • *****
  • Posts: 2264
  • Compiler Developer
Re: Unexpected FPC v3.0.4 Behavior
« Reply #20 on: June 27, 2020, 04:00:40 pm »
With none of the defines active, unlike FPC, Delphi v2.0 does _not_ compile the program.

Aside from the difference in behavior between Delphi and FPC, I do not see how FPC expects ParameterA to be used.  I suppose that, if it considers the sample program correct, there must be a way to use ParameterA but, it doesn't seem to like any of the expected constructions (and a few possibly unexpected ones too ;) (unexpected constructions not included in the sample.))

The behavior of FPC is rather logical once you know what it does (btw.: both TP 6 and Delphi 10.2 fail with "Type Identifier Expected"): the procedure ProcedureName is not a type. Thus it's not eligible to be used as a type for a parameter (or variable). Thus the compiler continues to search for ProcedureName and finds the type declaration for the array which is an eligible type.

You can confirm this by doing the following (of course you'll need to call both ProcedureName and ProcedureA):

Code: Pascal  [Select][+][-]
  1. procedure ProcedureName(ParameterA: ProcedureName);
  2. begin
  3. {$if FPC_FULLVERSION >= 30200}
  4.   Writeln(GetTypeKind(ParameterA));
  5. {$else}
  6.   Writeln(PTypeInfo(TypeInfo(ParameterA))^.Kind); // requires use of unit TypInfo
  7. {$endif}
  8. end;

This will print tkArray.

If I remember correctly this specific behavior is not documented for Delphi (or TP) and thus FPC is free to follow its own rules here.

TRon

  • Hero Member
  • *****
  • Posts: 536
Re: Unexpected FPC v3.0.4 Behavior
« Reply #21 on: June 27, 2020, 05:32:35 pm »
The behavior of FPC is rather logical once you know what it does (btw.: both TP 6 and Delphi 10.2 fail with "Type Identifier Expected"): the procedure ProcedureName is not a type. Thus it's not eligible to be used as a type for a parameter (or variable). Thus the compiler continues to search for ProcedureName and finds the type declaration for the array which is an eligible type.
...
If I remember correctly this specific behavior is not documented for Delphi (or TP) and thus FPC is free to follow its own rules here.
A precise and clear explanation  :)

However, it seems to be a bit more sophisticated than that.

If the compiler "selects" on type compatibility, taking the first eligible into account then why does the following fail ?
Code: Pascal  [Select][+][-]
  1. procedure ProcedureA;
  2.   procedure ProcedureName(ProcedureName : ProcedureName);
  3.   var
  4.     AnotherName : procedure(P: ProcedureName) is nested;
  5.   begin
  6.     AnotherName := @ProcedureName;
  7.   end;
  8. begin
  9. end;
  10.  
  11. Error: Incompatible types: got "Pointer" expected "<procedure variable type of procedure(ProcedureName) is nested;StdCall>"
  12.  

And since we're here, and on a more general note, should it (not) be possible to be able to reach the nested procedure using unitname.procedureA.ProcedureName ? (*)

edit: (*) By that i meant from the local point of view, not globally.
« Last Edit: June 27, 2020, 05:46:22 pm by TRon »

PascalDragon

  • Hero Member
  • *****
  • Posts: 2264
  • Compiler Developer
Re: Unexpected FPC v3.0.4 Behavior
« Reply #22 on: June 27, 2020, 05:57:57 pm »
The behavior of FPC is rather logical once you know what it does (btw.: both TP 6 and Delphi 10.2 fail with "Type Identifier Expected"): the procedure ProcedureName is not a type. Thus it's not eligible to be used as a type for a parameter (or variable). Thus the compiler continues to search for ProcedureName and finds the type declaration for the array which is an eligible type.
...
If I remember correctly this specific behavior is not documented for Delphi (or TP) and thus FPC is free to follow its own rules here.
A precise and clear explanation  :)

However, it seems to be a bit more sophisticated than that.

If the compiler "selects" on type compatibility, taking the first eligible into account then why does the following fail ?
Code: Pascal  [Select][+][-]
  1. procedure ProcedureA;
  2.   procedure ProcedureName(ProcedureName : ProcedureName);
  3.   var
  4.     AnotherName : procedure(P: ProcedureName) is nested;
  5.   begin
  6.     AnotherName := @ProcedureName;
  7.   end;
  8. begin
  9. end;
  10.  
  11. Error: Incompatible types: got "Pointer" expected "<procedure variable type of procedure(ProcedureName) is nested;StdCall>"
  12.  

The ProcedureName inside the declaration of AnotherName is still a type and thus only the array type declared in the unit/program scope is eligible. The pointer on the other hand is a pointer to the parameter ProcedureName, because that is closer in the symtable than the procedure ProcedureName. You can also see that if you enabled typed pointers using $T+. The error will then read got "^ProcedureName" expected .... If the right side would be a routine then you would have a routine header as error (e.g. <address of procedure(ProcedureName) is nested;Register>), not merely a type name.

Please note that if you have a function and the modeswitch Result is not enabled, but DuplicateLocals is (e.g. mode TP, but not mode FPC), then you won't be able to access the result variable, because the parameter hides the result variable.

Something inconsistent however happens if you declare the procedure ProcedureName as a function and have a local variable of type ProcedureName, then the compiler will complain about an error in the type definition. That should definitely be straightened out...

And since we're here, and on a more general note, should it (not) be possible to be able to reach the nested procedure using unitname.procedureA.ProcedureName ?

No, as this would potentially conflict with functions that return records or classes.

440bx

  • Hero Member
  • *****
  • Posts: 2017
Re: Unexpected FPC v3.0.4 Behavior
« Reply #23 on: June 27, 2020, 08:05:18 pm »
The behavior of FPC is rather logical once you know what it does (btw.: both TP 6 and Delphi 10.2 fail with "Type Identifier Expected"): the procedure ProcedureName is not a type. Thus it's not eligible to be used as a type for a parameter (or variable). Thus the compiler continues to search for ProcedureName and finds the type declaration for the array which is an eligible type.

That certainly raises a brow.  That's quite a departure from how scope resolution is supposed to work and opens the door to very subtle, very confusing and very bug prone constructs.

If I remember correctly this specific behavior is not documented for Delphi (or TP) and thus FPC is free to follow its own rules here.
I'd have to review the standards but, using type information to determine when the scope search should be ended isn't something I've ever seen mentioned in any Pascal standard.  Just wondering and curious now... is that how FPC implements function/procedure overloading ?... would that be the reason for that departure in the scope resolution method ?

Something inconsistent however happens if you declare the procedure ProcedureName as a function and have a local variable of type ProcedureName, then the compiler will complain about an error in the type definition. That should definitely be straightened out...
Using type information for scope resolution opens the door to many other inconsistencies, among them:
Code: Pascal  [Select][+][-]
  1. procedure B;
  2. var
  3.   B : integer;
  4. begin
  5.   if B = 1 then  // can access the variable but...
  6.   begin
  7.     dec(B);
  8.     B;           // using type to resolve scope should make this work.
  9.   end;
  10. end;
  11.  
  12. begin
  13. end.  
  14.  
According to the scope resolution rule you just mentioned, the compiler should be able to resolve "B;" as a procedure call, yet it does _not_ and putting "();" after it, thereby indicating it is a procedure call, makes no difference.

FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 2264
  • Compiler Developer
Re: Unexpected FPC v3.0.4 Behavior
« Reply #24 on: June 27, 2020, 09:18:39 pm »
The behavior of FPC is rather logical once you know what it does (btw.: both TP 6 and Delphi 10.2 fail with "Type Identifier Expected"): the procedure ProcedureName is not a type. Thus it's not eligible to be used as a type for a parameter (or variable). Thus the compiler continues to search for ProcedureName and finds the type declaration for the array which is an eligible type.

That certainly raises a brow.  That's quite a departure from how scope resolution is supposed to work and opens the door to very subtle, very confusing and very bug prone constructs.

Please note that I'm not saying that the behavior is good, I'm just saying how it is (and it would probably not be easy to solve either).

If I remember correctly this specific behavior is not documented for Delphi (or TP) and thus FPC is free to follow its own rules here.
I'd have to review the standards but, using type information to determine when the scope search should be ended isn't something I've ever seen mentioned in any Pascal standard.  Just wondering and curious now... is that how FPC implements function/procedure overloading ?... would that be the reason for that departure in the scope resolution method ?

FPC collects all routine bodies (in other units until one without overload is encountered) and then matches the provided parameters to the headers of that collection. If only one is left, perfect, otherwise it's an error. This has nothing to do with type resolution.

Something inconsistent however happens if you declare the procedure ProcedureName as a function and have a local variable of type ProcedureName, then the compiler will complain about an error in the type definition. That should definitely be straightened out...
Using type information for scope resolution opens the door to many other inconsistencies, among them:
Code: Pascal  [Select][+][-]
  1. procedure B;
  2. var
  3.   B : integer;
  4. begin
  5.   if B = 1 then  // can access the variable but...
  6.   begin
  7.     dec(B);
  8.     B;           // using type to resolve scope should make this work.
  9.   end;
  10. end;
  11.  
  12. begin
  13. end.  
  14.  
According to the scope resolution rule you just mentioned, the compiler should be able to resolve "B;" as a procedure call, yet it does _not_ and putting "();" after it, thereby indicating it is a procedure call, makes no difference.

The compiler differentiates between the body of a function and e.g. declarations. Slightly different rules can apply in these situations.

For example (different use case, but slightly related):

Code: Pascal  [Select][+][-]
  1. type
  2.   TRecord = record
  3.     hwnd: HWND;
  4.     hwnd2: HWND;
  5.   end;

This record declaration compiles in both FPC and Delphi. If the compiler would not differentiate between types and other symbols then the second field declaration would not be possible.

440bx

  • Hero Member
  • *****
  • Posts: 2017
Re: Unexpected FPC v3.0.4 Behavior
« Reply #25 on: June 27, 2020, 10:07:43 pm »
Please note that I'm not saying that the behavior is good, I'm just saying how it is (and it would probably not be easy to solve either).
I'm relieved you're not saying it's good.  Solving the problem isn't hard, it simply shouldn't accept "ProcedureName" as a type name.  Delphi doesn't accept it, therefore, if FPC didn't accept it either, it would not affect Delphi compatibility.

FPC collects all routine bodies (in other units until one without overload is encountered) and then matches the provided parameters to the headers of that collection. If only one is left, perfect, otherwise it's an error. This has nothing to do with type resolution.
That's good. 

The compiler differentiates between the body of a function and e.g. declarations. Slightly different rules can apply in these situations.

For example (different use case, but slightly related):

Code: Pascal  [Select][+][-]
  1. type
  2.   TRecord = record
  3.     hwnd: HWND;
  4.     hwnd2: HWND;
  5.   end;

This record declaration compiles in both FPC and Delphi. If the compiler would not differentiate between types and other symbols then the second field declaration would not be possible.
That's the kind of stuff that causes all kinds of ambiguous and inconsistent constructions.  In the example you showed above, FPC and Delphi, go looking for HWND as a type which is correct because in that definition TRecord isn't defined yet, therefore there is no conflict with the field named "hwnd" but, keeping the case exactly the same (scope-wise) but changing the types, leads to different results.  Just for fun, here is an example:
Code: Pascal  [Select][+][-]
  1. //{$DEFINE TEST_A}
  2. //{$DEFINE TEST_B}
  3.  
  4. { with both defines commented out, Delphi compiles this test but, FPC doesn't }
  5.  
  6. { with TEST_A enabled, neither FPC nor Delphi compile the test and the error  }
  7. { message emitted isn't true.                                                 }
  8.  
  9. { with TEST_B enabled, neither compiler accepts the test because integer is a }
  10. { boolean (thi8s test included just for fun)                                  }
  11.  
  12.  
  13. program TestFieldTypes;
  14.  
  15. type
  16.   TRECORD_TYPE_A = record
  17.     FieldA           : integer;
  18.     Integer          : boolean;
  19.   end;
  20.  
  21.   TRECORD_TYPE_B = record
  22.     FieldA           : integer;
  23.     Integer          : integer;
  24.   end;
  25.  
  26.   TRECORD_TYPE_C = record                { TRECORD_TYPE_C fully defined here  }
  27.     FieldA           : integer;
  28.     TRECORD_TYPE_C   : boolean;
  29.   end;
  30.  
  31.   integer        = boolean;
  32.  
  33.  
  34.   function FunWithTypes : boolean;
  35.   type
  36.     { complains that TRECORD_TYPE_C isn't fully defined but that's not true!  }
  37.     { both, Delphi and FPC fail this scope test                               }
  38.  
  39.     TRECORD_TYPE_C = record
  40.       FieldA            : integer;
  41.  
  42.       {$IFDEF TEST_A}
  43.          TRECORD_TYPE_C : TRECORD_TYPE_C  { Fully defined up there ^^^^^^^^^^^}
  44.       {$ENDIF}
  45.     end;
  46.  
  47.   begin
  48.     result := false;     { for some reason FPC doesn't like "result" here     }
  49.   end;
  50.  
  51.  
  52. var
  53.   AVariable : integer;   { it may look like an integer but, it isn't one      }
  54.  
  55. begin
  56.   {$IFDEF TEST_B}
  57.     AVariable := AVariable + 1;  // incompatible types, cannot add 1 to integers
  58.                                  // anymore!
  59.   {$ENDIF}
  60. end.
  61.  
Change the type from HWND to TRECORD_TYPE_C and things don't work the same way anymore.

Not really directly related to the topic but, combine the above with the ability of redefining standard types (such as integer) and some rather "interesting" results can be had.  Same applies to the presence of TRECORD_TYPE_A and TRECORD_TYPE_B (I simply couldn't resist.)

Talking about "result"(s),  why does FPC complain about "result" in the function ?... Delphi has no problem with it and, I don't see why FPC would.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Bart

  • Hero Member
  • *****
  • Posts: 3944
    • Bart en Mariska's Webstek
Re: Unexpected FPC v3.0.4 Behavior
« Reply #26 on: June 27, 2020, 10:17:04 pm »
Talking about "result"(s),  why does FPC complain about "result" in the function ?... Delphi has no problem with it and, I don't see why FPC would.

Needs {$mode objfpc  or delphi} or {$modeswitch result}

Bart

440bx

  • Hero Member
  • *****
  • Posts: 2017
Re: Unexpected FPC v3.0.4 Behavior
« Reply #27 on: June 27, 2020, 10:27:14 pm »
Needs {$mode objfpc  or delphi} or {$modeswitch result}
Thank you Bart.  I didn't realize that "result" needed to be "enabled" with a switch.
FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

PascalDragon

  • Hero Member
  • *****
  • Posts: 2264
  • Compiler Developer
Re: Unexpected FPC v3.0.4 Behavior
« Reply #28 on: June 28, 2020, 04:18:32 pm »
Please note that I'm not saying that the behavior is good, I'm just saying how it is (and it would probably not be easy to solve either).
I'm relieved you're not saying it's good.  Solving the problem isn't hard, it simply shouldn't accept "ProcedureName" as a type name.  Delphi doesn't accept it, therefore, if FPC didn't accept it either, it would not affect Delphi compatibility.

Solving the problem is not as easy as you think.

Change the type from HWND to TRECORD_TYPE_C and things don't work the same way anymore.

The compiler complains about the record not being fully defined, because the record type that is currently declared must be in scope to allow constructs like this:

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyRecord = record
  3.     f: ^TMyRecord;
  4.   end;

A pointer to the record can be used, but the non-pointer type is not allowed (except for method declarations when modeswitch AdvancedRecords is enabled, because they don't need the size of the record, yet).

Not really directly related to the topic but, combine the above with the ability of redefining standard types (such as integer) and some rather "interesting" results can be had.  Same applies to the presence of TRECORD_TYPE_A and TRECORD_TYPE_B (I simply couldn't resist.)

First of Integer is not a built in type, it's just an alias for SmallInt in unit System or LongInt in unit ObjPas (if modes Delphi, ObjFPC or MacPas are used). Second you can use builtin types without any problems, because they are in fact namespaced to the System unit. Thus you can declare a type LongInt = Boolean, but continue to use the builtin type using System.LongInt. The compiler even allows you to redefine some apparent keywords like Break or Continue (and yes, that was a fun debug session when I discovered that back when I still used Delphi 6 :o ).

Talking about "result"(s),  why does FPC complain about "result" in the function ?... Delphi has no problem with it and, I don't see why FPC would.

The default mode of FPC is mode FPC which is essentially a mode TP with some extensions (like overloading). Use of the Result variable is only enabled in the "modern" modes like Delphi or ObjFPC or if modeswitch Result is used.

 

TinyPortal © 2005-2018