fcl-pssrc, pastojs: fixed anonymous proc inside for-loop

This commit is contained in:
mattias 2019-06-06 09:51:37 +00:00
parent b692d76fc5
commit 849d604c04
5 changed files with 287 additions and 223 deletions
compiler/packages

View File

@ -1467,7 +1467,6 @@ type
procedure ResolveImplElement(El: TPasImplElement); virtual;
procedure ResolveImplCaseOf(CaseOf: TPasImplCaseOf); virtual;
procedure ResolveImplLabelMark(Mark: TPasImplLabelMark); virtual;
procedure ResolveImplForLoop(Loop: TPasImplForLoop); virtual;
procedure ResolveImplWithDo(El: TPasImplWithDo); virtual;
procedure ResolveImplAsm(El: TPasImplAsmStatement); virtual;
procedure ResolveImplAssign(El: TPasImplAssign); virtual;
@ -1525,6 +1524,7 @@ type
procedure FinishExceptOnExpr; virtual;
procedure FinishExceptOnStatement; virtual;
procedure FinishWithDo(El: TPasImplWithDo); virtual;
procedure FinishForLoopHeader(Loop: TPasImplForLoop); virtual;
procedure FinishDeclaration(El: TPasElement); virtual;
procedure FinishVariable(El: TPasVariable); virtual;
procedure FinishProperty(PropEl: TPasProperty); virtual;
@ -6390,6 +6390,224 @@ begin
PopWithScope(El);
end;
procedure TPasResolver.FinishForLoopHeader(Loop: TPasImplForLoop);
var
VarResolved, StartResolved, EndResolved,
OrigStartResolved: TPasResolverResult;
EnumeratorFound, HasInValues: Boolean;
InRange, VarRange: TResEvalValue;
InRangeInt, VarRangeInt: TResEvalRangeInt;
bt: TResolverBaseType;
TypeEl, ElType: TPasType;
C: TClass;
begin
CreateScope(Loop,TPasForLoopScope);
// loop var
ResolveExpr(Loop.VariableName,rraReadAndAssign);
ComputeElement(Loop.VariableName,VarResolved,[rcNoImplicitProc,rcSetReferenceFlags]);
if not ResolvedElCanBeVarParam(VarResolved,Loop.VariableName) then
RaiseVarExpected(20170216151955,Loop.VariableName,VarResolved.IdentEl);
// resolve start expression
ResolveExpr(Loop.StartExpr,rraRead);
ComputeElement(Loop.StartExpr,StartResolved,[rcSetReferenceFlags]);
case Loop.LoopType of
ltNormal,ltDown:
begin
// start value
if CheckAssignResCompatibility(VarResolved,StartResolved,Loop.StartExpr,true)=cIncompatible then
RaiseIncompatibleTypeRes(20170216151958,nIncompatibleTypesGotExpected,
[],StartResolved,VarResolved,Loop.StartExpr);
CheckAssignExprRange(VarResolved,Loop.StartExpr);
// end value
ResolveExpr(Loop.EndExpr,rraRead);
ComputeElement(Loop.EndExpr,EndResolved,[rcSetReferenceFlags]);
if CheckAssignResCompatibility(VarResolved,EndResolved,Loop.EndExpr,false)=cIncompatible then
RaiseIncompatibleTypeRes(20170216152001,nIncompatibleTypesGotExpected,
[],EndResolved,VarResolved,Loop.EndExpr);
CheckAssignExprRange(VarResolved,Loop.EndExpr);
end;
ltIn:
begin
// check range
EnumeratorFound:=CheckForIn(Loop,VarResolved,StartResolved);
if (not EnumeratorFound)
and not (StartResolved.IdentEl is TPasType)
and (rrfReadable in StartResolved.Flags) then
begin
EnumeratorFound:=CheckForInClassOrRec(Loop,VarResolved,StartResolved);
end;
if not EnumeratorFound then
begin
VarRange:=nil;
InRange:=nil;
try
OrigStartResolved:=StartResolved;
if StartResolved.IdentEl is TPasType then
begin
// e.g. for e in TEnum do
TypeEl:=StartResolved.LoTypeEl;
if TypeEl is TPasArrayType then
begin
if length(TPasArrayType(TypeEl).Ranges)=1 then
InRange:=Eval(TPasArrayType(TypeEl).Ranges[0],[refConst]);
end;
if InRange=nil then
InRange:=EvalTypeRange(TypeEl,[]);
{$IFDEF VerbosePasResolver}
{AllowWriteln}
if InRange<>nil then
writeln('TPasResolver.ResolveImplForLoop in type: InRange=',InRange.AsDebugString)
else
writeln('TPasResolver.ResolveImplForLoop in type: InRange=nil');
{AllowWriteln-}
{$ENDIF}
end
else if rrfReadable in StartResolved.Flags then
begin
// value (variable or expression)
bt:=StartResolved.BaseType;
if bt in [btSet,btArrayOrSet] then
begin
if (StartResolved.IdentEl=nil) and (StartResolved.ExprEl<>nil) then
InRange:=Eval(StartResolved.ExprEl,[]);
if InRange=nil then
InRange:=EvalTypeRange(StartResolved.LoTypeEl,[]);
end
else if bt=btContext then
begin
TypeEl:=StartResolved.LoTypeEl;
C:=TypeEl.ClassType;
if C=TPasArrayType then
begin
ElType:=GetArrayElType(TPasArrayType(TypeEl));
ComputeElement(ElType,StartResolved,[rcType]);
StartResolved.Flags:=OrigStartResolved.Flags*[rrfReadable,rrfWritable];
if CheckAssignResCompatibility(VarResolved,StartResolved,Loop.StartExpr,true)=cIncompatible then
RaiseIncompatibleTypeRes(20171112210138,nIncompatibleTypesGotExpected,
[],StartResolved,VarResolved,Loop.StartExpr);
EnumeratorFound:=true;
end;
end
else
begin
bt:=GetActualBaseType(bt);
case bt of
{$ifdef FPC_HAS_CPSTRING}
btAnsiString:
InRange:=TResEvalRangeInt.CreateValue(revskChar,nil,0,$ff);
{$endif}
btUnicodeString:
InRange:=TResEvalRangeInt.CreateValue(revskChar,nil,0,$ffff);
end;
end;
end;
if (not EnumeratorFound) and (InRange<>nil) then
begin
// for v in <constant> do
// -> check if same type
VarRange:=EvalTypeRange(VarResolved.LoTypeEl,[]);
if VarRange=nil then
RaiseXExpectedButYFound(20171109191528,'range',
GetResolverResultDescription(VarResolved),Loop.VariableName);
//writeln('TPasResolver.ResolveImplForLoop ForIn VarRange=',VarRange.AsDebugString);
//writeln('TPasResolver.ResolveImplForLoop ForIn InRange=',InRange.AsDebugString,' ElType=',GetResolverResultDbg(StartResolved));
case InRange.Kind of
revkRangeInt,revkSetOfInt:
begin
InRangeInt:=TResEvalRangeInt(InRange);
case VarRange.Kind of
revkRangeInt:
begin
VarRangeInt:=TResEvalRangeInt(VarRange);
HasInValues:=(InRange.Kind<>revkSetOfInt) or (length(TResEvalSet(InRange).Ranges)>0);
case InRangeInt.ElKind of
revskEnum:
if (VarRangeInt.ElKind<>revskEnum)
or not IsSameType(InRangeInt.ElType,VarRangeInt.ElType,prraAlias) then
RaiseXExpectedButYFound(20171109200752,GetTypeDescription(InRangeInt.ElType),
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskInt:
if VarRangeInt.ElKind<>revskInt then
RaiseXExpectedButYFound(20171109200752,'integer',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskChar:
if VarRangeInt.ElKind<>revskChar then
RaiseXExpectedButYFound(20171109200753,'char',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskBool:
if VarRangeInt.ElKind<>revskBool then
RaiseXExpectedButYFound(20171109200754,'boolean',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
else
if HasInValues then
RaiseNotYetImplemented(20171109200954,Loop.StartExpr);
end;
if HasInValues then
begin
if (VarRangeInt.RangeStart>InRangeInt.RangeStart) then
begin
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRangeInt.AsDebugString,' ',InRangeInt.AsDebugString);
{$ENDIF}
fExprEvaluator.EmitRangeCheckConst(20171109201428,
InRangeInt.ElementAsString(InRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeEnd),Loop.VariableName,mtError);
end;
if (VarRangeInt.RangeEnd<InRangeInt.RangeEnd) then
begin
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRangeInt.AsDebugString,' ',InRangeInt.AsDebugString);
{$ENDIF}
fExprEvaluator.EmitRangeCheckConst(20171109201429,
InRangeInt.ElementAsString(InRangeInt.RangeEnd),
VarRangeInt.ElementAsString(VarRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeEnd),Loop.VariableName,mtError);
end;
end;
EnumeratorFound:=true;
end;
else
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop ForIn VarRange=',VarRange.AsDebugString);
{$ENDIF}
end;
end;
else
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop ForIn InRange=',InRange.AsDebugString);
{$ENDIF}
end;
end;
if not EnumeratorFound then
begin
{$IFDEF VerbosePasResolver}
{AllowWriteln}
writeln('TPasResolver.ResolveImplForLoop StartResolved=',GetResolverResultDbg(StartResolved));
if VarRange<>nil then
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRange.AsDebugString);
{AllowWriteln-}
{$ENDIF}
RaiseMsg(20171108223818,nCannotFindEnumeratorForType,sCannotFindEnumeratorForType,
[GetBaseDescription(OrigStartResolved)],Loop.StartExpr);
end;
finally
ReleaseEvalValue(VarRange);
ReleaseEvalValue(InRange);
end;
end;
end;
else
RaiseNotYetImplemented(20171108221334,Loop);
end;
end;
procedure TPasResolver.FinishDeclaration(El: TPasElement);
var
C: TClass;
@ -7788,7 +8006,8 @@ begin
else if C=TPasImplLabelMark then
ResolveImplLabelMark(TPasImplLabelMark(El))
else if C=TPasImplForLoop then
ResolveImplForLoop(TPasImplForLoop(El))
// the header was already resolved
ResolveImplElement(TPasImplForLoop(El).Body)
else if C=TPasImplTry then
begin
ResolveImplBlock(TPasImplTry(El));
@ -8131,225 +8350,6 @@ begin
RaiseNotYetImplemented(20161014141636,Mark);
end;
procedure TPasResolver.ResolveImplForLoop(Loop: TPasImplForLoop);
var
VarResolved, StartResolved, EndResolved,
OrigStartResolved: TPasResolverResult;
EnumeratorFound, HasInValues: Boolean;
InRange, VarRange: TResEvalValue;
InRangeInt, VarRangeInt: TResEvalRangeInt;
bt: TResolverBaseType;
TypeEl, ElType: TPasType;
C: TClass;
begin
CreateScope(Loop,TPasForLoopScope);
// loop var
ResolveExpr(Loop.VariableName,rraReadAndAssign);
ComputeElement(Loop.VariableName,VarResolved,[rcNoImplicitProc,rcSetReferenceFlags]);
if not ResolvedElCanBeVarParam(VarResolved,Loop.VariableName) then
RaiseVarExpected(20170216151955,Loop.VariableName,VarResolved.IdentEl);
// resolve start expression
ResolveExpr(Loop.StartExpr,rraRead);
ComputeElement(Loop.StartExpr,StartResolved,[rcSetReferenceFlags]);
case Loop.LoopType of
ltNormal,ltDown:
begin
// start value
if CheckAssignResCompatibility(VarResolved,StartResolved,Loop.StartExpr,true)=cIncompatible then
RaiseIncompatibleTypeRes(20170216151958,nIncompatibleTypesGotExpected,
[],StartResolved,VarResolved,Loop.StartExpr);
CheckAssignExprRange(VarResolved,Loop.StartExpr);
// end value
ResolveExpr(Loop.EndExpr,rraRead);
ComputeElement(Loop.EndExpr,EndResolved,[rcSetReferenceFlags]);
if CheckAssignResCompatibility(VarResolved,EndResolved,Loop.EndExpr,false)=cIncompatible then
RaiseIncompatibleTypeRes(20170216152001,nIncompatibleTypesGotExpected,
[],EndResolved,VarResolved,Loop.EndExpr);
CheckAssignExprRange(VarResolved,Loop.EndExpr);
end;
ltIn:
begin
// check range
EnumeratorFound:=CheckForIn(Loop,VarResolved,StartResolved);
if (not EnumeratorFound)
and not (StartResolved.IdentEl is TPasType)
and (rrfReadable in StartResolved.Flags) then
begin
EnumeratorFound:=CheckForInClassOrRec(Loop,VarResolved,StartResolved);
end;
if not EnumeratorFound then
begin
VarRange:=nil;
InRange:=nil;
try
OrigStartResolved:=StartResolved;
if StartResolved.IdentEl is TPasType then
begin
// e.g. for e in TEnum do
TypeEl:=StartResolved.LoTypeEl;
if TypeEl is TPasArrayType then
begin
if length(TPasArrayType(TypeEl).Ranges)=1 then
InRange:=Eval(TPasArrayType(TypeEl).Ranges[0],[refConst]);
end;
if InRange=nil then
InRange:=EvalTypeRange(TypeEl,[]);
{$IFDEF VerbosePasResolver}
{AllowWriteln}
if InRange<>nil then
writeln('TPasResolver.ResolveImplForLoop in type: InRange=',InRange.AsDebugString)
else
writeln('TPasResolver.ResolveImplForLoop in type: InRange=nil');
{AllowWriteln-}
{$ENDIF}
end
else if rrfReadable in StartResolved.Flags then
begin
// value (variable or expression)
bt:=StartResolved.BaseType;
if bt in [btSet,btArrayOrSet] then
begin
if (StartResolved.IdentEl=nil) and (StartResolved.ExprEl<>nil) then
InRange:=Eval(StartResolved.ExprEl,[]);
if InRange=nil then
InRange:=EvalTypeRange(StartResolved.LoTypeEl,[]);
end
else if bt=btContext then
begin
TypeEl:=StartResolved.LoTypeEl;
C:=TypeEl.ClassType;
if C=TPasArrayType then
begin
ElType:=GetArrayElType(TPasArrayType(TypeEl));
ComputeElement(ElType,StartResolved,[rcType]);
StartResolved.Flags:=OrigStartResolved.Flags*[rrfReadable,rrfWritable];
if CheckAssignResCompatibility(VarResolved,StartResolved,Loop.StartExpr,true)=cIncompatible then
RaiseIncompatibleTypeRes(20171112210138,nIncompatibleTypesGotExpected,
[],StartResolved,VarResolved,Loop.StartExpr);
EnumeratorFound:=true;
end;
end
else
begin
bt:=GetActualBaseType(bt);
case bt of
{$ifdef FPC_HAS_CPSTRING}
btAnsiString:
InRange:=TResEvalRangeInt.CreateValue(revskChar,nil,0,$ff);
{$endif}
btUnicodeString:
InRange:=TResEvalRangeInt.CreateValue(revskChar,nil,0,$ffff);
end;
end;
end;
if (not EnumeratorFound) and (InRange<>nil) then
begin
// for v in <constant> do
// -> check if same type
VarRange:=EvalTypeRange(VarResolved.LoTypeEl,[]);
if VarRange=nil then
RaiseXExpectedButYFound(20171109191528,'range',
GetResolverResultDescription(VarResolved),Loop.VariableName);
//writeln('TPasResolver.ResolveImplForLoop ForIn VarRange=',VarRange.AsDebugString);
//writeln('TPasResolver.ResolveImplForLoop ForIn InRange=',InRange.AsDebugString,' ElType=',GetResolverResultDbg(StartResolved));
case InRange.Kind of
revkRangeInt,revkSetOfInt:
begin
InRangeInt:=TResEvalRangeInt(InRange);
case VarRange.Kind of
revkRangeInt:
begin
VarRangeInt:=TResEvalRangeInt(VarRange);
HasInValues:=(InRange.Kind<>revkSetOfInt) or (length(TResEvalSet(InRange).Ranges)>0);
case InRangeInt.ElKind of
revskEnum:
if (VarRangeInt.ElKind<>revskEnum)
or not IsSameType(InRangeInt.ElType,VarRangeInt.ElType,prraAlias) then
RaiseXExpectedButYFound(20171109200752,GetTypeDescription(InRangeInt.ElType),
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskInt:
if VarRangeInt.ElKind<>revskInt then
RaiseXExpectedButYFound(20171109200752,'integer',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskChar:
if VarRangeInt.ElKind<>revskChar then
RaiseXExpectedButYFound(20171109200753,'char',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
revskBool:
if VarRangeInt.ElKind<>revskBool then
RaiseXExpectedButYFound(20171109200754,'boolean',
GetResolverResultDescription(VarResolved,true),loop.VariableName);
else
if HasInValues then
RaiseNotYetImplemented(20171109200954,Loop.StartExpr);
end;
if HasInValues then
begin
if (VarRangeInt.RangeStart>InRangeInt.RangeStart) then
begin
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRangeInt.AsDebugString,' ',InRangeInt.AsDebugString);
{$ENDIF}
fExprEvaluator.EmitRangeCheckConst(20171109201428,
InRangeInt.ElementAsString(InRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeEnd),Loop.VariableName,mtError);
end;
if (VarRangeInt.RangeEnd<InRangeInt.RangeEnd) then
begin
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRangeInt.AsDebugString,' ',InRangeInt.AsDebugString);
{$ENDIF}
fExprEvaluator.EmitRangeCheckConst(20171109201429,
InRangeInt.ElementAsString(InRangeInt.RangeEnd),
VarRangeInt.ElementAsString(VarRangeInt.RangeStart),
VarRangeInt.ElementAsString(VarRangeInt.RangeEnd),Loop.VariableName,mtError);
end;
end;
EnumeratorFound:=true;
end;
else
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop ForIn VarRange=',VarRange.AsDebugString);
{$ENDIF}
end;
end;
else
{$IFDEF VerbosePasResolver}
writeln('TPasResolver.ResolveImplForLoop ForIn InRange=',InRange.AsDebugString);
{$ENDIF}
end;
end;
if not EnumeratorFound then
begin
{$IFDEF VerbosePasResolver}
{AllowWriteln}
writeln('TPasResolver.ResolveImplForLoop StartResolved=',GetResolverResultDbg(StartResolved));
if VarRange<>nil then
writeln('TPasResolver.ResolveImplForLoop VarRange=',VarRange.AsDebugString);
{AllowWriteln-}
{$ENDIF}
RaiseMsg(20171108223818,nCannotFindEnumeratorForType,sCannotFindEnumeratorForType,
[GetBaseDescription(OrigStartResolved)],Loop.StartExpr);
end;
finally
ReleaseEvalValue(VarRange);
ReleaseEvalValue(InRange);
end;
end;
end;
else
RaiseNotYetImplemented(20171108221334,Loop);
end;
ResolveImplElement(Loop.Body);
end;
procedure TPasResolver.ResolveImplWithDo(El: TPasImplWithDo);
// Note: the expressions were already resolved during parsing
// and the scopes were already stored in a TPasWithScope.
@ -16309,6 +16309,7 @@ begin
stExceptOnExpr: FinishExceptOnExpr;
stExceptOnStatement: FinishExceptOnStatement;
stWithExpr: FinishWithDo(El as TPasImplWithDo);
stForLoopHeader: FinishForLoopHeader(El as TPasImplForLoop);
stDeclaration: FinishDeclaration(El);
stAncestors: FinishAncestors(El as TPasClassType);
stInitialFinalization: FinishInitialFinalization(El as TPasImplBlock);

View File

@ -174,7 +174,8 @@ type
stWithExpr, // calls BeginScope after parsing every WITH-expression
stExceptOnExpr,
stExceptOnStatement,
stDeclaration, // e.g. a TPasProperty, TPasVariable, TPasArgument
stForLoopHeader,
stDeclaration, // e.g. a TPasProperty, TPasVariable, TPasArgument, ...
stAncestors, // the list of ancestors and interfaces of a class
stInitialFinalization
);
@ -5735,6 +5736,7 @@ begin
TPasImplForLoop(El).LoopType:=lt;
if (CurToken<>tkDo) then
ParseExcTokenError(TokenInfos[tkDo]);
Engine.FinishScope(stForLoopHeader,El);
CreateBlock(TPasImplForLoop(El));
El:=nil;
//WriteLn(i,'FOR "',VarName,'" := ',StartValue,' to ',EndValue,' Token=',CurTokenText);

View File

@ -469,6 +469,7 @@ type
Procedure TestAnonymousProc_With;
Procedure TestAnonymousProc_ExceptOn;
Procedure TestAnonymousProc_Nested;
Procedure TestAnonymousProc_ForLoop;
// record
Procedure TestRecord;
@ -7683,6 +7684,27 @@ begin
ParseProgram;
end;
procedure TTestResolver.TestAnonymousProc_ForLoop;
begin
StartProgram(false);
Add([
'type TProc = reference to procedure;',
'procedure Foo(p: TProc);',
'begin',
'end;',
'procedure DoIt;',
'var i: word;',
' a: word;',
'begin',
' for i:=1 to 10 do begin',
' Foo(procedure begin a:=3; end);',
' end;',
'end;',
'begin',
' DoIt;']);
ParseProgram;
end;
procedure TTestResolver.TestRecord;
begin
StartProgram(false);

View File

@ -16723,7 +16723,7 @@ end;
function TPasToJSConverter.CreateRTTIMemberProperty(Prop: TPasProperty;
AContext: TConvertContext): TJSElement;
// create $r.addProperty("propname",flags,result,"getter","setter",{options})
// create $r.addProperty("propname",flags,proptype,"getter","setter",{options})
var
Call: TJSCallExpression;
OptionsEl: TJSObjectLiteral;

View File

@ -347,6 +347,7 @@ type
Procedure TestAnonymousProc_Nested;
Procedure TestAnonymousProc_NestedAssignResult;
Procedure TestAnonymousProc_Class;
Procedure TestAnonymousProc_ForLoop;
// enums, sets
Procedure TestEnum_Name;
@ -4789,6 +4790,44 @@ begin
'']));
end;
procedure TTestModule.TestAnonymousProc_ForLoop;
begin
StartProgram(false);
Add([
'type TProc = reference to procedure;',
'procedure Foo(p: TProc);',
'begin',
'end;',
'procedure DoIt;',
'var i: word;',
' a: word;',
'begin',
' for i:=1 to 10 do begin',
' Foo(procedure begin a:=3; end);',
' end;',
'end;',
'begin',
' DoIt;']);
ConvertProgram;
CheckSource('TestAnonymousProc_ForLoop',
LinesToStr([ // statements
'this.Foo = function (p) {',
'};',
'this.DoIt = function () {',
' var i = 0;',
' var a = 0;',
' for (i = 1; i <= 10; i++) {',
' $mod.Foo(function () {',
' a = 3;',
' });',
' };',
'};',
'']),
LinesToStr([
'$mod.DoIt();'
]));
end;
procedure TTestModule.TestEnum_Name;
begin
StartProgram(false);