View Issue Details

IDProjectCategoryView StatusLast Update
0038088LazarusIDEpublic2020-11-21 08:41
ReporterOkobaPatino Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityhave not tried
Status closedResolutionfixed 
Product Version2.1 (SVN) 
Summary0038088: JCF fails generic types using const
DescriptionWhen a type like this is declared, JCF can not continue to resolve code in any case, Both in mode objfpc and Delphi (without the generic keyword).
generic TTest<T; const S: integer> = array [0..S] of T;
Tagsgenerics
Fixed in Revisionr64149
LazTarget-
Widgetset
Attached Files

Relationships

related to 0038087 new CodeTools: Code completion can not resolve generic types using const 

Activities

Juha Manninen

2020-11-16 23:25

developer   ~0126991

FPC also fails to compile it. Your generic syntax looks odd. Are you saying that Delphi compiles it?

OkobaPatino

2020-11-17 06:55

reporter   ~0126994

Last edited: 2020-11-17 07:00

View 2 revisions

FPC has this feature in Trunk, and it is working in mode objfpc and Delphi. This feature is not supported in Delphi.
https://lists.freepascal.org/pipermail/fpc-devel/2020-April/042708.html

Domingo Galmés

2020-11-19 22:20

reporter   ~0127063

This patch adds support for generic types using const and for the operators << shift left and >> shift right.

Sample code taken from freepascal tests for testing.
freepascal/tests/test/tgenconst1.pp

{$mode objfpc}
{
    test all possible constants
}
program tgenconst1;

type
    TEnums = (Blaise, Pascal);
    kNames = set of TEnums;
    kChars = set of char;
 
type
    generic TBoolean<const U: boolean> = record end;
    generic TString<const U: string> = record end;
    generic TFloat<const U: single> = record end;
    generic TInteger<const U: integer> = record end;
    generic TChar<const U: char> = record end;
    generic TByte<const U: byte> = record end;
    generic TQWord<const U: QWord> = record end;
    generic TEnum<const U: TEnums> = record end;
    generic TNames<const U: kNames> = record end;
    generic TChars<const U: kChars> = record end;
    generic TPointer<const U: pointer> = record end;

var
    a: specialize TBoolean<true>;
    b: specialize TString<'string'>;
    c: specialize TFloat<1>;
    d: specialize TInteger<10>;
    e: specialize TByte<255>;
    f: specialize TChar<'a'>;
    g: specialize TEnum<Pascal>;
    h: specialize TNames<[Blaise,Pascal]>;
    i: specialize TChars<['a','b']>;
    j: specialize TQWord<10>;
    k: specialize TPointer<nil>;
begin
end.
JCF_generic_constants.patch (13,061 bytes)   
From 4b646ddbf0570bdbf0351963e9901c834071d7a1 Mon Sep 17 00:00:00 2001
From: DomingoGP <dgalmesp@gmail.com>
Date: Thu, 19 Nov 2020 22:09:25 +0100
Subject: [PATCH] Add support for generic constants and operators shl  <<  shr 
 >>.

---
 components/jcf2/Parse/BuildParseTree.pas      | 135 +++++++++++++++---
 components/jcf2/Parse/BuildTokenList.pas      |  19 ++-
 components/jcf2/Parse/TokenUtils.pas          |   5 +-
 components/jcf2/Parse/Tokens.pas              |  10 +-
 .../jcf2/Process/Returns/ReturnAfter.pas      |   7 +
 .../jcf2/Process/Spacing/NoSpaceAfter.pas     |   6 +
 .../jcf2/Process/Spacing/NoSpaceBefore.pas    |   5 +
 .../jcf2/Process/Spacing/SingleSpaceAfter.pas |   4 +-
 components/jcf2/TestApplication/unit1.lfm     |   2 +-
 9 files changed, 167 insertions(+), 26 deletions(-)

diff --git a/components/jcf2/Parse/BuildParseTree.pas b/components/jcf2/Parse/BuildParseTree.pas
index a871f959ef..196b3e0fce 100644
--- a/components/jcf2/Parse/BuildParseTree.pas
+++ b/components/jcf2/Parse/BuildParseTree.pas
@@ -65,6 +65,7 @@ type
     fiTokenCount: integer;
     procedure RecogniseTypeHelper;
     procedure SplitGreaterThanOrEqual;
+    procedure SplitShr_gg;
 
     procedure RecogniseGoal;
     procedure RecogniseUnit;
@@ -1260,6 +1261,9 @@ begin
   PopNode;
 end;
 
+const
+  CONST_GENERIC_TOKENS = [ttAt, ttOpenBracket, ttOpenSquareBracket, ttIdentifier, ttPlus, ttMinus,
+        ttNot, ttNumber, ttQuotedLiteralString, ttNil, ttTrue, ttFalse];
 
 function TBuildParseTree.GenericAhead: boolean;
 var
@@ -1287,7 +1291,7 @@ begin
     if liTokenIndex mod 2 = 0 then
     begin
       // should be id
-      if (lcToken.WordType <> wtBuiltInType) and (not IsIdentifierToken(lcToken, idAny)) then
+      if (lcToken.WordType <> wtBuiltInType) and (not IsIdentifierToken(lcToken, idAny)) and (not (lcToken.TokenType in CONST_GENERIC_TOKENS)) then
       begin
         break;
       end;
@@ -1320,36 +1324,102 @@ end;
 
 
 const
-  ConstraintTokens = [ttClass, ttRecord, ttConstructor];
+  ConstraintTokens = [ttClass, ttRecord, ttConstructor, ttInterface, ttObject];
 
 procedure TBuildParseTree.RecogniseGenericType;
+var
+  lbHasConst: boolean;
+  lbIsGenericType:boolean;
+
+  procedure RecogniseP;
+  var
+    liNestLevel: integer;
+  begin
+    if fcTokenList.FirstSolidTokenType = ttConst then
+    begin
+      Recognise(ttConst);
+      lbHasConst := True;
+    end;
+    if lbHasConst = False then //can be a expresion like    h: specialize TNames<[Blaise,Pascal]>;
+    begin
+      lbisGenericType:=(fcTokenList.FirstSolidTokenType=ttIdentifier) and (fcTokenList.SolidTokenType(2)=ttLessThan); //is generic type
+      if fcTokenList.FirstSolidTokenType=ttSpecialize then
+        lbIsGenericType:=true;
+      if (fcTokenList.FirstSolidTokenType in CONST_GENERIC_TOKENS) and (not lbIsGenericType) then
+      begin //hack. recognise tokens until ; , or >
+        liNestLevel := 0;
+        while (liNestLevel > 0) or (not (fcTokenList.FirstTokenType in [ttComma, ttGreaterThan, ttSemiColon, ttGreaterThanOrEqual, ttShr_gg])) do
+        begin
+          if fcTokenList.FirstTokenType in [ttOpenSquareBracket, ttOpenBracket] then
+            Inc(liNestLevel);
+          if fcTokenList.FirstTokenType in [ttCloseSquareBracket, ttCloseBracket] then
+            Dec(liNestLevel);
+          Recognise(fcTokenList.FirstTokenType);
+          if fcTokenList.EOF then
+          begin
+            raise TEParseError.Create('Unexpected EOF. ',nil);
+          end;
+        end;
+      end
+      else
+        RecogniseType;
+    end
+    else
+      RecogniseType;
+
+    if lbHasConst then
+    begin
+      if fcTokenList.FirstSolidTokenType = ttColon then
+        RecogniseGenericConstraints;
+    end;
+  end;
+
 begin
   PushNode(nGeneric);
 
   // angle brackets
   Recognise(ttLessThan);
-  RecogniseType;
 
-  if fcTokenList.FirstSolidTokenType = ttColon then
+  while True do
   begin
-    RecogniseGenericConstraints;
+    lbHasConst := False;
+    PushNode(nType);
+    RecogniseP;
+    // more types after commas
+    while fcTokenList.FirstSolidTokenType = ttComma do
+    begin
+      Recognise(ttComma);
+      RecogniseP;
+    end;
+
+    if fcTokenList.FirstSolidTokenType = ttColon then
+    begin
+      RecogniseGenericConstraints;
+    end;
+    if fcTokenList.FirstSolidTokenType <> ttSemiColon then
+    begin
+      PopNode;
+      break;
+    end;
+    Recognise(ttSemiColon);
+    PopNode;
   end;
 
-   // more types after commas
-   while fcTokenList.FirstSolidTokenType = ttComma do
-   begin
-      Recognise(ttComma);
-      RecogniseType;
-   end;
+  if fcTokenList.FirstSolidTokenType = ttGreaterThanOrEqual then
+  begin
+    // the tokenizer got it wrong - e.g "TTestNullable<T:Record>=Class"
+    // this is the same as TTestNullable<T:Record> =Class
+    RecogniseWhiteSpace;
+    SplitGreaterThanOrEqual;
+  end;
 
-   if  fcTokenList.FirstSolidTokenType = ttGreaterThanOrEqual  then
-   begin
-     // the tokenizer got it wrong - e.g "TTestNullable<T:Record>=Class"
-     // this is the same as TTestNullable<T:Record> =Class
-     RecogniseWhiteSpace;
+  if fcTokenList.FirstSolidTokenType = ttShr_gg then
+  begin
+    // >> operator
+    RecogniseWhiteSpace;
+    SplitShr_gg;
+  end;
 
-     SplitGreaterThanOrEqual;
-   end;
 
   Recognise(ttGreaterThan);
 
@@ -1421,6 +1491,35 @@ begin
   end;
 end;
 
+procedure TBuildParseTree.SplitShr_gg;
+var
+  liIndex: integer;
+  lcNewToken: TSourceToken;
+  fsFileName: string;
+begin
+  if fcTokenList.FirstTokenType = ttShr_gg then
+  begin
+    liIndex := fcTokenList.CurrentTokenIndex;
+    fsFileName := fcTokenList.SourceTokens[liIndex].FileName;
+
+    fcTokenList.Delete(liIndex);
+
+    lcNewToken := TSourceToken.Create();
+    lcNewToken.FileName := fsFileName;
+    lcNewToken.SourceCode := '>';
+    lcNewToken.TokenType := ttGreaterThan;
+
+    fcTokenList.Insert(liIndex, lcNewToken);
+
+    lcNewToken := TSourceToken.Create();
+    lcNewToken.FileName := fsFileName;
+    lcNewToken.SourceCode := '>';
+    lcNewToken.TokenType := ttGreaterThan;
+
+    fcTokenList.Insert(liIndex + 1 , lcNewToken);
+  end;
+end;
+
 
 { helper proc for RecogniseTypedConstant
   need to distinguish
diff --git a/components/jcf2/Parse/BuildTokenList.pas b/components/jcf2/Parse/BuildTokenList.pas
index e2e0fbce9f..962a5fb59e 100644
--- a/components/jcf2/Parse/BuildTokenList.pas
+++ b/components/jcf2/Parse/BuildTokenList.pas
@@ -63,6 +63,7 @@ type
     function ForwardChar(const piOffset: integer): Char;
     function ForwardChars(const piOffset, piCount: integer): String;
     procedure Consume(const piCount: integer = 1);
+    procedure UndoConsume(const piCount: integer = 1);
     function EndOfFile: boolean;
     function EndOfFileAfter(const piChars: integer): boolean;
 
@@ -820,10 +821,15 @@ function TBuildTokenList.TryPunctuation(const pcToken: TSourceToken): boolean;
     // "<<" is the start of two nested generics,
     // likewise '>>' is not an operator, it is two "end-of-generic" signs in sucession
     if (chLast = '<') and (ch = '<') then
+    begin
+      Result := True;  // <<
       exit;
+    end;
     if (chLast = '>') and (ch = '>') then
+    begin
+      Result := True; // >>
       exit;
-
+    end;
 
     Result := CharIsPuncChar(ch);
   end;
@@ -851,6 +857,12 @@ begin
     Consume;
   end;
 
+  if length(pcToken.SourceCode) > 2 then  // nested generic    specialize TC1<TC2<TC3<integer>>>=record  end;
+  begin
+    // only consume the first >
+    UndoConsume(Length(pcToken.SourceCode) - 1);
+    pcToken.SourceCode := pcToken.SourceCode[1];
+  end;
   { try to recognise the punctuation as an operator }
   TypeOfToken(pcToken.SourceCode, leWordType, leTokenType);
   if leTokenType <> ttUnknown then
@@ -932,6 +944,11 @@ begin
   inc(fiCurrentIndex, piCount);
 end;
 
+procedure TBuildTokenList.UndoConsume(const piCount: integer);
+begin
+  dec(fiCurrentIndex, piCount);
+end;
+
 function TBuildTokenList.EndOfFile: boolean;
 begin
   Result := fiCurrentIndex > Length(fsSourceCode);
diff --git a/components/jcf2/Parse/TokenUtils.pas b/components/jcf2/Parse/TokenUtils.pas
index 98172b8274..83041e1050 100644
--- a/components/jcf2/Parse/TokenUtils.pas
+++ b/components/jcf2/Parse/TokenUtils.pas
@@ -640,9 +640,12 @@ begin
 end;
 
 function IsDfmIncludeDirective(const pt: TSourceToken): boolean;
+var
+  lsToken:string;
 begin
   // form dfm comment
-  Result := (pt.TokenType = ttComment) and AnsiSameText(pt.SourceCode, '{$R *.dfm}') and
+  lsToken:=UpperCase(pt.SourceCode);
+  Result := (pt.TokenType = ttComment) and ((lsToken = '{$R *.DFM}') or (lsToken = '[$R *.LFM}')) and
     pt.HasParentNode(nImplementationSection, 4);
 end;
 
diff --git a/components/jcf2/Parse/Tokens.pas b/components/jcf2/Parse/Tokens.pas
index 7e60edddd1..14c0a650b5 100644
--- a/components/jcf2/Parse/Tokens.pas
+++ b/components/jcf2/Parse/Tokens.pas
@@ -295,7 +295,9 @@ type
     ttPlusAssign,     // +=
     ttMinusAssign,    // -=
     ttTimesAssign,    // *=
-    ttFloatDivAssign  // /=
+    ttFloatDivAssign, // /=
+    ttShl_ll,         // <<
+    ttShr_gg          // >>
     );
 
   TTokenTypeSet = set of TTokenType;
@@ -419,7 +421,7 @@ const
 
   AddOperators: TTokenTypeSet = [ttPlus, ttMinus, ttOr, ttXor];
 
-  MulOperators: TTokenTypeSet = [ttTimes, ttFloatDiv, ttDiv, ttMod, ttAnd, ttShl, ttShr, ttExponent];
+  MulOperators: TTokenTypeSet = [ttTimes, ttFloatDiv, ttDiv, ttMod, ttAnd, ttShl, ttShr, ttExponent, ttShl_ll,ttShr_gg];
 
   SingleSpaceOperators = [
     // some unary operators
@@ -427,7 +429,7 @@ const
     // all operators that are always binary
     ttAnd, ttAs, ttDiv, ttIn, ttIs, ttMod, ttOr, ttShl, ttShr, ttXor,
     ttTimes, ttFloatDiv, ttExponent, ttEquals, ttGreaterThan, ttLessThan,
-    ttGreaterThanOrEqual, ttLessThanOrEqual, ttNotEqual, ttSetSymDif];
+    ttGreaterThanOrEqual, ttLessThanOrEqual, ttNotEqual, ttSetSymDif, ttShl_ll, ttShr_gg];
 
   StringWords: TTokenTypeSet = [ttString, ttAnsiString, ttWideString];
 
@@ -789,6 +791,8 @@ begin
   AddKeyword('<=', wtOperator, ttLessThanOrEqual);
   AddKeyword('<>', wtOperator, ttNotEqual);
   AddKeyword('><', wtOperator, ttSetSymDif);
+  AddKeyword('<<', wtOperator, ttShl_ll);  // in FreePascal
+  AddKeyword('>>', wtOperator, ttShr_gg);  // in FreePascal
   // these must come after the above as they are shorter
   AddKeyword('>', wtOperator, ttGreaterThan);
   AddKeyword('<', wtOperator, ttLessThan);
diff --git a/components/jcf2/Process/Returns/ReturnAfter.pas b/components/jcf2/Process/Returns/ReturnAfter.pas
index 3e277f4ae7..34b8e189f9 100644
--- a/components/jcf2/Process/Returns/ReturnAfter.pas
+++ b/components/jcf2/Process/Returns/ReturnAfter.pas
@@ -128,6 +128,13 @@ begin
     Result := True;
     exit;
   end;
+  { in generic definition}
+  if pt.HasParentNode(nGeneric,2) then
+  begin
+    Result := False;
+    exit;
+  end;
+
 end;
 
 
diff --git a/components/jcf2/Process/Spacing/NoSpaceAfter.pas b/components/jcf2/Process/Spacing/NoSpaceAfter.pas
index ebcea3d262..5c2d54e3ef 100644
--- a/components/jcf2/Process/Spacing/NoSpaceAfter.pas
+++ b/components/jcf2/Process/Spacing/NoSpaceAfter.pas
@@ -71,6 +71,12 @@ begin
   if ptNext.TokenType = ttComment then
     exit;
 
+  if (pt.TokenType in [ttLessThan,ttGreaterThan]) and pt.HasParentNode(nGeneric,1) then
+  begin
+    Result := True;
+    Exit;
+  end;
+
   if pt.TokenType in NoSpaceAnywhere then
   begin
     Result := True;
diff --git a/components/jcf2/Process/Spacing/NoSpaceBefore.pas b/components/jcf2/Process/Spacing/NoSpaceBefore.pas
index ede1816a8b..b2220f6417 100644
--- a/components/jcf2/Process/Spacing/NoSpaceBefore.pas
+++ b/components/jcf2/Process/Spacing/NoSpaceBefore.pas
@@ -78,6 +78,11 @@ begin
     exit;
   end;
 
+  if (pt.TokenType in [ttLessThan,ttGreaterThan]) and pt.HasParentNode(nGeneric,1) then
+  begin
+    Result := True;
+    Exit;
+  end;
 
   // '@@' in asm, e.g. "JE @@initTls" needs the space
   if pt.HasParentNode(nAsm) then
diff --git a/components/jcf2/Process/Spacing/SingleSpaceAfter.pas b/components/jcf2/Process/Spacing/SingleSpaceAfter.pas
index 2793470e65..50cbd1da2d 100644
--- a/components/jcf2/Process/Spacing/SingleSpaceAfter.pas
+++ b/components/jcf2/Process/Spacing/SingleSpaceAfter.pas
@@ -80,9 +80,9 @@ begin
   if pt.HasParentNode(nAsm) then
     exit;
 
-  if pt.HasParentNode(nGeneric, 1) then
+  if pt.HasParentNode(nGeneric, 2) then
   begin
-    if pt.TokenType in [ttComma, ttColon] then
+    if pt.TokenType in [ttComma, ttColon, ttSemiColon] then
     begin
       Result := true;
     end;
diff --git a/components/jcf2/TestApplication/unit1.lfm b/components/jcf2/TestApplication/unit1.lfm
index 6856ef7a07..3f5be659ee 100644
--- a/components/jcf2/TestApplication/unit1.lfm
+++ b/components/jcf2/TestApplication/unit1.lfm
@@ -1150,7 +1150,7 @@ object Form1: TForm1
     Left = 18
     Height = 15
     Top = 465
-    Width = 80
+    Width = 152
     Anchors = [akLeft, akBottom]
     AutoSize = False
     ParentColor = False
-- 
2.29.1.windows.1

JCF_generic_constants.patch (13,061 bytes)   

Juha Manninen

2020-11-19 23:13

developer   ~0127065

Applied, thanks.

OkobaPatino

2020-11-21 08:41

reporter   ~0127079

Works well.
Thank you.

Issue History

Date Modified Username Field Change
2020-11-16 17:47 OkobaPatino New Issue
2020-11-16 23:25 Juha Manninen Note Added: 0126991
2020-11-16 23:26 Juha Manninen Relationship added related to 0038087
2020-11-17 06:55 OkobaPatino Note Added: 0126994
2020-11-17 07:00 OkobaPatino Note Edited: 0126994 View Revisions
2020-11-17 14:37 OkobaPatino Tag Attached: generics
2020-11-19 22:20 Domingo Galmés Note Added: 0127063
2020-11-19 22:20 Domingo Galmés File Added: JCF_generic_constants.patch
2020-11-19 22:31 Juha Manninen Assigned To => Juha Manninen
2020-11-19 22:31 Juha Manninen Status new => assigned
2020-11-19 23:13 Juha Manninen Status assigned => resolved
2020-11-19 23:13 Juha Manninen Resolution open => fixed
2020-11-19 23:13 Juha Manninen Fixed in Revision => r64149
2020-11-19 23:13 Juha Manninen LazTarget => -
2020-11-19 23:13 Juha Manninen Note Added: 0127065
2020-11-21 08:41 OkobaPatino Status resolved => closed
2020-11-21 08:41 OkobaPatino Note Added: 0127079