fix(semantic): set equality S=[] and S=[lit] no longer crash
AnalyseBinaryExpr crashed with a nil-deref when one operand was a set type (tySet) and the other was an empty array literal [] — because AnalyseArrayLiteralExpr returns nil for an empty literal (no element type to infer), and the set-dispatch guard read .Kind off that nil pointer. Fix: before the set-dispatch guard, coerce any array literal on either side (empty or non-empty) to the set type of the opposite operand, mirroring the existing pattern for `x in [A, B, C]`. This also enables `S = [fA, fB]` without requiring an explicit cast. The native-backend set-variable crash documented in bugs.txt was already fixed in HEAD (the release binary produced a codegen error, but the current compiler handles it correctly). Tests added: - TSetTests.TestSemantic_Set_EqualityEmptyLiteral_OK — S=[] must not crash - TSetTests.TestSemantic_Set_EqualityLiteral_OK — S=[fA,fB] accepted - TSetTests.TestCodegen_Set_EqualityEmptyLiteralEmitsCeqw — IR check - TSetTests.TestCodegen_Set_EqualityLiteralEmitsCeqw — IR check - TE2EMiscTests.TestRun_Set_EqualityWithLiteral — compile+run assertion - TE2ENativeTests.TestRun_ZeroInit_SetLocal now runs on both backends
This commit is contained in:
parent
a2d263e0e8
commit
75db9584dc
|
|
@ -8938,18 +8938,31 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Set arithmetic and equality when both operands are set types }
|
||||
if (LType.Kind = tySet) or (RType.Kind = tySet) then
|
||||
{ Set arithmetic and equality: coerce array literals [...] or empty []
|
||||
to the set type of the other operand. AnalyseArrayLiteralExpr returns nil
|
||||
for [] (no element type to infer) and tyOpenArray for [a, b, c]. }
|
||||
if (LType <> nil) and (LType.Kind = tySet) and
|
||||
(ABin.Right is TArrayLiteralExpr) and
|
||||
((RType = nil) or (RType.Kind = tyOpenArray)) then
|
||||
RType := AnalyseSetLiteralExpr(TArrayLiteralExpr(ABin.Right),
|
||||
TSetTypeDesc(LType));
|
||||
if (RType <> nil) and (RType.Kind = tySet) and
|
||||
(ABin.Left is TArrayLiteralExpr) and
|
||||
((LType = nil) or (LType.Kind = tyOpenArray)) then
|
||||
LType := AnalyseSetLiteralExpr(TArrayLiteralExpr(ABin.Left),
|
||||
TSetTypeDesc(RType));
|
||||
if ((LType <> nil) and (LType.Kind = tySet)) or
|
||||
((RType <> nil) and (RType.Kind = tySet)) then
|
||||
begin
|
||||
if LType.Kind <> tySet then
|
||||
if (LType = nil) or (LType.Kind <> tySet) then
|
||||
SemanticError(
|
||||
Format('Left operand of ''%s'' must be a set type, got ''%s''',
|
||||
[BinaryOpName(ABin.Op), LType.Name]),
|
||||
Format('Left operand of ''%s'' must be a set type',
|
||||
[BinaryOpName(ABin.Op)]),
|
||||
ABin.Line, ABin.Col);
|
||||
if RType.Kind <> tySet then
|
||||
if (RType = nil) or (RType.Kind <> tySet) then
|
||||
SemanticError(
|
||||
Format('Right operand of ''%s'' must be a set type, got ''%s''',
|
||||
[BinaryOpName(ABin.Op), RType.Name]),
|
||||
Format('Right operand of ''%s'' must be a set type',
|
||||
[BinaryOpName(ABin.Op)]),
|
||||
ABin.Line, ABin.Col);
|
||||
if LType <> RType then
|
||||
SemanticError(
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ type
|
|||
procedure TestRun_Set_UnionIntersect;
|
||||
procedure TestRun_Set_ValuedConstant;
|
||||
procedure TestRun_Set_LiteralArgument;
|
||||
procedure TestRun_Set_EqualityWithLiteral;
|
||||
|
||||
{ 64-bit sets (>32 members) }
|
||||
procedure TestRun_Set64_InOperator_HighBit;
|
||||
|
|
@ -452,6 +453,22 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
{ Set equality: S = [] and S = [literal] comparisons. }
|
||||
SrcSetEquality = '''
|
||||
program Prg;
|
||||
type TDir = (North, South, East, West);
|
||||
TDirs = set of TDir;
|
||||
var S: TDirs;
|
||||
begin
|
||||
S := [];
|
||||
if S = [] then WriteLn('empty') else WriteLn('not-empty');
|
||||
S := [East, West];
|
||||
if S = [] then WriteLn('bad-empty') else WriteLn('not-empty2');
|
||||
if S = [East, West] then WriteLn('match') else WriteLn('no-match');
|
||||
if S <> [East, West] then WriteLn('bad-ne') else WriteLn('equal')
|
||||
end.
|
||||
''';
|
||||
|
||||
BigEnum64 =
|
||||
'''
|
||||
type
|
||||
|
|
@ -865,6 +882,16 @@ begin
|
|||
'no-N' + LE + 'E' + LE + 'no-N' + LE + 'no-E' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TE2EMiscTests.TestRun_Set_EqualityWithLiteral;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcSetEquality, Output, RCode));
|
||||
AssertEquals('exit code 0', 0, RCode);
|
||||
AssertEquals('set equality with literals',
|
||||
'empty' + LE + 'not-empty2' + LE + 'match' + LE + 'equal' + LE, Output);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ 64-bit sets (>32 members) }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
|
|
@ -385,9 +385,7 @@ type
|
|||
starts at its zero value even on a dirty stack. Each test category uses
|
||||
a Dirty() helper that fills the stack with 0xDEADBEEF garbage before the
|
||||
real procedure runs, proving that the zero-init comes from the prologue
|
||||
and not from lucky stack layout.
|
||||
Note: TestRun_ZeroInit_SetLocal runs QBE-only because the native backend
|
||||
currently crashes the compiler on set-typed variables (bugs.txt). }
|
||||
and not from lucky stack layout. }
|
||||
procedure TestRun_ZeroInit_ScalarIntegers;
|
||||
procedure TestRun_ZeroInit_FloatLocals;
|
||||
procedure TestRun_ZeroInit_BooleanAndChar;
|
||||
|
|
@ -6067,19 +6065,9 @@ begin
|
|||
end;
|
||||
|
||||
procedure TE2ENativeTests.TestRun_ZeroInit_SetLocal;
|
||||
var
|
||||
Stdout: string;
|
||||
ExitCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
{ Native backend crashes the compiler on set-typed variables — QBE only
|
||||
until that pre-existing bug is fixed (see bugs.txt).
|
||||
Note: set equality (S = []) also crashes the semantic pass (separate bug),
|
||||
so we probe zero-init via the 'in' operator instead. }
|
||||
if not CompileAndRun(SrcZeroInit_SetLocal, Stdout, ExitCode) then
|
||||
Fail('QBE compile/run failed');
|
||||
AssertEquals('0' + LE, Stdout);
|
||||
AssertEquals(0, ExitCode);
|
||||
AssertRunsOnAll(SrcZeroInit_SetLocal, '0' + LE, 0);
|
||||
end;
|
||||
|
||||
procedure TE2ENativeTests.TestRun_ZeroInit_RecordWithMixedFields;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ type
|
|||
procedure TestSemantic_Set_Difference_OK;
|
||||
procedure TestSemantic_Set_Intersection_OK;
|
||||
procedure TestSemantic_Set_Equality_OK;
|
||||
procedure TestSemantic_Set_EqualityEmptyLiteral_OK;
|
||||
procedure TestSemantic_Set_EqualityLiteral_OK;
|
||||
procedure TestSemantic_Set_BaseTypeMustBeEnum;
|
||||
procedure TestSemantic_Set_LiteralElementMustMatchBase;
|
||||
|
||||
|
|
@ -68,6 +70,8 @@ type
|
|||
procedure TestCodegen_Set_IntersectionEmitsAnd;
|
||||
procedure TestCodegen_Set_EqualityEmitsCeqw;
|
||||
procedure TestCodegen_Set_InequalityEmitsCnew;
|
||||
procedure TestCodegen_Set_EqualityEmptyLiteralEmitsCeqw;
|
||||
procedure TestCodegen_Set_EqualityLiteralEmitsCeqw;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ set-valued constants (const X = [a, b]) }
|
||||
|
|
@ -361,6 +365,28 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
SrcSetEqualityEmptyLiteral =
|
||||
'program P;' + #10 +
|
||||
DirEnum +
|
||||
'''
|
||||
var S: TDirSet; B: Boolean;
|
||||
begin
|
||||
S := [];
|
||||
B := S = []
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcSetEqualityLiteral =
|
||||
'program P;' + #10 +
|
||||
DirEnum +
|
||||
'''
|
||||
var S: TDirSet; B: Boolean;
|
||||
begin
|
||||
S := [dNorth, dEast];
|
||||
B := S = [dNorth, dEast]
|
||||
end.
|
||||
''';
|
||||
|
||||
BigEnum =
|
||||
'''
|
||||
type
|
||||
|
|
@ -767,6 +793,18 @@ begin
|
|||
SemanticOK(SrcSetInequality);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_EqualityEmptyLiteral_OK;
|
||||
begin
|
||||
{ S = [] must not crash the semantic pass (was nil-deref before fix). }
|
||||
SemanticOK(SrcSetEqualityEmptyLiteral);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_EqualityLiteral_OK;
|
||||
begin
|
||||
{ S = [dNorth, dEast] — literal RHS coerced to the set type of LHS. }
|
||||
SemanticOK(SrcSetEqualityLiteral);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestSemantic_Set_BaseTypeMustBeEnum;
|
||||
begin
|
||||
SemanticFail(SrcSetBadBaseType);
|
||||
|
|
@ -898,6 +936,24 @@ begin
|
|||
AssertTrue('set inequality emits cnew', Pos('cnew', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestCodegen_Set_EqualityEmptyLiteralEmitsCeqw;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ S = [] coerces [] to the set type of S; bitmask 0 vs S — still ceqw. }
|
||||
IR := GenIR(SrcSetEqualityEmptyLiteral);
|
||||
AssertTrue('S = [] emits ceqw', Pos('ceqw', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSetTests.TestCodegen_Set_EqualityLiteralEmitsCeqw;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ S = [dNorth, dEast] coerces the literal to mask 5; comparison is ceqw. }
|
||||
IR := GenIR(SrcSetEqualityLiteral);
|
||||
AssertTrue('S = [dNorth, dEast] emits ceqw', Pos('ceqw', IR) > 0);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ set-valued constants }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
Loading…
Reference in a new issue