From cd2d13552f3c3100b7c0d668d2622d221e2f2b97 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 27 Sep 2024 07:32:35 +0200 Subject: [PATCH] Multiple crashbug fixes (#491) * More inputs * Update DoesNotCrashTests.cs * Update DoesNotCrashTests.cs * Update ConstraintSolver_Operations.cs * Rename * Update ConstraintSolver_Operations.cs * Crashfix * Update ConstraintSolver_Rules.cs * Update ConstraintSolver_Rules.cs * Update BoundNode.cs * Added missing error check * Update Binder_Statement.cs * More bugfix * Return expr crashfix --- src/Draco.Compiler.Fuzzer/inputs/gol.draco | 78 +++++++++ .../inputs/simple_list.draco | 11 ++ .../inputs/variadic_args.draco | 9 ++ .../EndToEnd/DoesNotCrashTests.cs | 5 + .../Semantics/SymbolResolutionTests.cs | 21 +++ .../Semantics/TypeCheckingTests.cs | 45 ++++++ .../Api/Syntax/SyntaxFactory.cs | 1 + .../Internal/Binding/Binder_Common.cs | 9 +- .../Internal/Binding/Binder_Expression.cs | 26 ++- .../Internal/Binding/Binder_Statement.cs | 7 +- .../Internal/Binding/Binder_Type.cs | 9 ++ .../Binding/SymbolResolutionErrors.cs | 9 ++ .../Internal/Binding/TypeCheckingErrors.cs | 9 ++ .../Internal/BoundTree/BoundNode.cs | 3 +- .../Solver/ConstraintSolver_Operations.cs | 150 ++++++++++++------ .../Internal/Solver/ConstraintSolver_Rules.cs | 28 ++-- 16 files changed, 348 insertions(+), 72 deletions(-) create mode 100644 src/Draco.Compiler.Fuzzer/inputs/gol.draco create mode 100644 src/Draco.Compiler.Fuzzer/inputs/simple_list.draco create mode 100644 src/Draco.Compiler.Fuzzer/inputs/variadic_args.draco diff --git a/src/Draco.Compiler.Fuzzer/inputs/gol.draco b/src/Draco.Compiler.Fuzzer/inputs/gol.draco new file mode 100644 index 000000000..e9f5f1f20 --- /dev/null +++ b/src/Draco.Compiler.Fuzzer/inputs/gol.draco @@ -0,0 +1,78 @@ +import System.Console; +import System.Linq.Enumerable; + +val width = 80; +val height = 40; + +func render(map: Array2D) { + for (y in Range(0, height)) { + for (x in Range(0, width)) { + val cell = map[x, y]; + Write(if (cell) "o" else " "); + } + WriteLine(); + } +} + +func in_bounds(x: int32, y: int32): bool = + 0 <= x < width + and 0 <= y < height; + +func count_neighbors(x: int32, y: int32, map: Array2D): int32 { + func count_cell(x: int32, y: int32, map: Array2D): int32 = + if (in_bounds(x, y) and map[x, y]) 1 + else 0; + + var n = 0; + n += count_cell(x - 1, y - 1, map); + n += count_cell(x - 1, y, map); + n += count_cell(x - 1, y + 1, map); + n += count_cell(x, y - 1, map); + n += count_cell(x, y + 1, map); + n += count_cell(x + 1, y - 1, map); + n += count_cell(x + 1, y, map); + n += count_cell(x + 1, y + 1, map); + return n; +} + +func tick(front: Array2D, back: Array2D) { + for (y in Range(0, height)) { + for (x in Range(0, width)) { + // Any live cell with two or three live neighbours survives. + // Any dead cell with three live neighbours becomes a live cell. + // All other live cells die in the next generation. Similarly, all other dead cells stay dead. + val neighbors = count_neighbors(x, y, front); + back[x, y] = + if (front[x, y]) 2 <= neighbors <= 3 + else if (neighbors == 3) true + else false; + } + WriteLine(); + } +} + +public func main() { + var front = Array2D(width, height); + var back = Array2D(width, height); + + // Glider + front[3, 3] = true; + front[4, 4] = true; + front[4, 5] = true; + front[3, 5] = true; + front[2, 5] = true; + + while (true) { + tick(front, back); + + Clear(); + render(front); + + // Swap + val temp = front; + front = back; + back = temp; + + System.Threading.Thread.Sleep(200); + } +} diff --git a/src/Draco.Compiler.Fuzzer/inputs/simple_list.draco b/src/Draco.Compiler.Fuzzer/inputs/simple_list.draco new file mode 100644 index 000000000..a486a4602 --- /dev/null +++ b/src/Draco.Compiler.Fuzzer/inputs/simple_list.draco @@ -0,0 +1,11 @@ +import System.Collections.Generic; +import System.Linq.Enumerable; +import System.Console; + +func main() { + val l = List(); + for (i in Range(0, 100)) { + l.Add(i); + } + WriteLine("Count = \{l.Count}"); +} diff --git a/src/Draco.Compiler.Fuzzer/inputs/variadic_args.draco b/src/Draco.Compiler.Fuzzer/inputs/variadic_args.draco new file mode 100644 index 000000000..7e2be5ce4 --- /dev/null +++ b/src/Draco.Compiler.Fuzzer/inputs/variadic_args.draco @@ -0,0 +1,9 @@ +import System.Console; + +func arrayOf(...vs: Array): Array = vs; + +func main() { + for (s in arrayOf("abc", "def", "ghi")) { + WriteLine(s); + } +} diff --git a/src/Draco.Compiler.Tests/EndToEnd/DoesNotCrashTests.cs b/src/Draco.Compiler.Tests/EndToEnd/DoesNotCrashTests.cs index 3462a14ea..520cb2c84 100644 --- a/src/Draco.Compiler.Tests/EndToEnd/DoesNotCrashTests.cs +++ b/src/Draco.Compiler.Tests/EndToEnd/DoesNotCrashTests.cs @@ -225,6 +225,11 @@ func main() { System.Console(); } """)] + [InlineData(""" + import System.Collections.Generic; + + func f() = List(); + """)] [Theory] public void DoesNotCrash(string source) { diff --git a/src/Draco.Compiler.Tests/Semantics/SymbolResolutionTests.cs b/src/Draco.Compiler.Tests/Semantics/SymbolResolutionTests.cs index d8496922c..10d940332 100644 --- a/src/Draco.Compiler.Tests/Semantics/SymbolResolutionTests.cs +++ b/src/Draco.Compiler.Tests/Semantics/SymbolResolutionTests.cs @@ -4057,4 +4057,25 @@ public struct TestEnumerator Assert.Single(diags); AssertDiagnostics(diags, SymbolResolutionErrors.NotGettableProperty); } + + [Fact] + public void ReturningInGlobalBlockIsIllegal() + { + // val a = { return 4; }; + + var main = SyntaxTree.Create(CompilationUnit( + ImmutableVariableDeclaration( + "a", + null, + BlockExpression(ExpressionStatement(ReturnExpression(LiteralExpression(4))))))); + + // Act + var compilation = CreateCompilation(main); + var semanticModel = compilation.GetSemanticModel(main); + var diags = semanticModel.Diagnostics; + + // Assert + Assert.Single(diags); + AssertDiagnostics(diags, SymbolResolutionErrors.IllegalReturn); + } } diff --git a/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs b/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs index e47e9512b..8ea31d7ce 100644 --- a/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs +++ b/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs @@ -2402,4 +2402,49 @@ public void NonCallableReportedAsIllegalExpression() Assert.Single(diags); AssertDiagnostics(diags, TypeCheckingErrors.IllegalExpression); } + + [Fact] + public void GenericTypeNotInstantiatedInTypeContextIsAnError() + { + // func foo(a: Array2D) {} + + var main = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "foo", + ParameterList(Parameter("a", NameType("Array2D"))), + null, + BlockFunctionBody()))); + + // Act + var compilation = CreateCompilation(main); + var semanticModel = compilation.GetSemanticModel(main); + var diags = semanticModel.Diagnostics; + + // Assert + Assert.Single(diags); + AssertDiagnostics(diags, TypeCheckingErrors.GenericTypeNotInstantiated); + } + + [Fact] + public void TypeInStringInterpolationIsAnError() + { + // func foo(): string = "\{char}"; + + var main = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "foo", + ParameterList(), + NameType("string"), + InlineFunctionBody(StringExpression( + LineStringStart, + [InterpolationStringPart(InterpolationStart, NameExpression("char"), InterpolationEnd)], + LineStringEnd))))); + + // Act + var compilation = CreateCompilation(main); + var semanticModel = compilation.GetSemanticModel(main); + var diags = semanticModel.Diagnostics; + + // Assert + Assert.Single(diags); + AssertDiagnostics(diags, TypeCheckingErrors.IllegalExpression); + } } diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index d1308f96f..a0bbccf59 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -263,6 +263,7 @@ public static TextStringPartSyntax TextStringPart(string value) => public static SyntaxToken LineStringStart { get; } = Token(TokenKind.LineStringStart, "\""); public static SyntaxToken LineStringEnd { get; } = Token(TokenKind.LineStringEnd, "\""); + public static SyntaxToken InterpolationStart { get; } = Token(TokenKind.InterpolationStart, @"\{"); private static SyntaxToken Token(TokenKind tokenKind) => Internal.Syntax.SyntaxToken.From(tokenKind).ToRedNode(null!, null, 0); diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Common.cs b/src/Draco.Compiler/Internal/Binding/Binder_Common.cs index c66bbc47d..880f4f632 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Common.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Common.cs @@ -19,8 +19,13 @@ protected virtual void ConstraintReturnType( ConstraintSolver constraints, DiagnosticBag diagnostics) { - var containingFunction = (FunctionSymbol?)this.ContainingSymbol; - Debug.Assert(containingFunction is not null); + if (this.ContainingSymbol is not FunctionSymbol containingFunction) + { + diagnostics.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalReturn, + location: returnSyntax.Location)); + return; + } var returnTypeSyntax = (containingFunction as SyntaxFunctionSymbol)?.DeclaringSyntax.ReturnType?.Type; constraints.Assignable( containingFunction.ReturnType, diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs b/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs index 6662baea3..4c9da31c1 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs @@ -18,6 +18,27 @@ namespace Draco.Compiler.Internal.Binding; internal partial class Binder { + /// + /// Binds a syntax node to a value-producing expression. + /// If the expression is not a value-producing expression, an error is reported. + /// + /// The syntax to bind. + /// The constraints that has been collected during the binding process. + /// The diagnostics produced during the process. + /// + private async BindingTask BindExpressionToValueProducingExpression(SyntaxNode syntax, ConstraintSolver constraints, DiagnosticBag diagnostics) + { + var result = await this.BindExpression(syntax, constraints, diagnostics); + if (result.Type is null) + { + diagnostics.Add(Diagnostic.Create( + template: TypeCheckingErrors.IllegalExpression, + location: syntax.Location)); + return new BoundUnexpectedExpression(syntax); + } + return result; + } + /// /// Binds the given syntax node to an untyped expression. /// @@ -101,9 +122,8 @@ private async BindingTask BindStringExpression(StringExpression } case InterpolationStringPartSyntax interpolation: { - partsTask.Add(BindingTask.FromResult(new BoundStringInterpolation( - syntax, - await this.BindExpression(interpolation.Expression, constraints, diagnostics)))); + var expr = await this.BindExpressionToValueProducingExpression(interpolation.Expression, constraints, diagnostics); + partsTask.Add(BindingTask.FromResult(new BoundStringInterpolation(syntax, expr))); lastNewline = false; break; } diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Statement.cs b/src/Draco.Compiler/Internal/Binding/Binder_Statement.cs index fe1999b13..094abc445 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Statement.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Statement.cs @@ -23,7 +23,9 @@ internal partial class Binder protected virtual BindingTask BindStatement(SyntaxNode syntax, ConstraintSolver constraints, DiagnosticBag diagnostics) => syntax switch { // NOTE: The syntax error is already reported - UnexpectedFunctionBodySyntax or UnexpectedStatementSyntax => FromResult(new BoundUnexpectedStatement(syntax)), + UnexpectedFunctionBodySyntax + or UnexpectedStatementSyntax + or UnexpectedDeclarationSyntax => FromResult(new BoundUnexpectedStatement(syntax)), // Ignored ImportDeclarationSyntax => FromResult(BoundNoOpStatement.Default), FunctionDeclarationSyntax func => this.BindFunctionDeclaration(func, constraints, diagnostics), @@ -48,8 +50,7 @@ private BindingTask BindFunctionDeclaration(FunctionDeclarationS private async BindingTask BindExpressionStatement(ExpressionStatementSyntax syntax, ConstraintSolver constraints, DiagnosticBag diagnostics) { - var exprTask = this.BindExpression(syntax.Expression, constraints, diagnostics); - _ = exprTask.GetResultType(syntax.Expression, constraints, diagnostics); + var exprTask = this.BindExpressionToValueProducingExpression(syntax.Expression, constraints, diagnostics); return new BoundExpressionStatement(syntax, await exprTask); } diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Type.cs b/src/Draco.Compiler/Internal/Binding/Binder_Type.cs index c24f5f616..6bf64973c 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Type.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Type.cs @@ -23,6 +23,15 @@ internal TypeSymbol BindTypeToTypeSymbol(TypeSyntax syntax, DiagnosticBag diagno var symbol = this.BindType(syntax, diagnostics); if (symbol is TypeSymbol type) { + // For example referencing to Array2D without the generic arguments + if (type.IsGenericDefinition) + { + diagnostics.Add(Diagnostic.Create( + template: TypeCheckingErrors.GenericTypeNotInstantiated, + location: syntax.Location, + formatArgs: type)); + return WellKnownTypes.ErrorType; + } // Ok return type; } diff --git a/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs b/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs index a22e00212..491d07f92 100644 --- a/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs +++ b/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs @@ -184,4 +184,13 @@ internal static class SymbolResolutionErrors severity: DiagnosticSeverity.Error, format: "the {0} {1} is inaccessible due to its visibility", code: Code(19)); + + /// + /// The return expression is illegal in the current context. + /// + public static readonly DiagnosticTemplate IllegalReturn = DiagnosticTemplate.Create( + title: "illegal return", + severity: DiagnosticSeverity.Error, + format: "illegal return expression outside of function definition", + code: Code(20)); } diff --git a/src/Draco.Compiler/Internal/Binding/TypeCheckingErrors.cs b/src/Draco.Compiler/Internal/Binding/TypeCheckingErrors.cs index 28b8c3cc2..c5a079030 100644 --- a/src/Draco.Compiler/Internal/Binding/TypeCheckingErrors.cs +++ b/src/Draco.Compiler/Internal/Binding/TypeCheckingErrors.cs @@ -156,5 +156,14 @@ internal static class TypeCheckingErrors severity: DiagnosticSeverity.Error, format: "the attribute {0} can not be applied multiple times to the same element", code: Code(16)); + + /// + /// The generic type was not instantiated in a type context. + /// + public static readonly DiagnosticTemplate GenericTypeNotInstantiated = DiagnosticTemplate.Create( + title: "generic type not instantiated", + severity: DiagnosticSeverity.Error, + format: "the generic type {0} is not instantiated", + code: Code(17)); } diff --git a/src/Draco.Compiler/Internal/BoundTree/BoundNode.cs b/src/Draco.Compiler/Internal/BoundTree/BoundNode.cs index 97428d335..944b8115f 100644 --- a/src/Draco.Compiler/Internal/BoundTree/BoundNode.cs +++ b/src/Draco.Compiler/Internal/BoundTree/BoundNode.cs @@ -154,7 +154,8 @@ internal partial class BoundDelegateCreationExpression internal partial class BoundArrayAccessExpression { - public override TypeSymbol Type => this.Array.TypeRequired.GenericArguments[0]; + public override TypeSymbol Type => this.Array.TypeRequired.GenericArguments.FirstOrDefault() + ?? WellKnownTypes.ErrorType; } internal partial class BoundArrayLengthExpression diff --git a/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Operations.cs b/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Operations.cs index c3493c266..6705c42b4 100644 --- a/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Operations.cs +++ b/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Operations.cs @@ -10,6 +10,82 @@ namespace Draco.Compiler.Internal.Solver; internal sealed partial class ConstraintSolver { + // Assignability /////////////////////////////////////////////////////////// + + /// + /// Assigns a type to another, asserting their success. + /// + /// The target type to assign to. + /// The assigned type. + public static void AssignAsserted(TypeSymbol targetType, TypeSymbol assignedType) + { + if (Assign(targetType, assignedType)) return; + throw new InvalidOperationException($"could not assign {assignedType} to {targetType}"); + } + + /// + /// Assigns a type to anoter. + /// + /// The target type to assign to. + /// The assigned type. + /// True, if the assignment was successful, false otherwise. + private static bool Assign(TypeSymbol targetType, TypeSymbol assignedType) => + AssignRecursionScheme(targetType, assignedType, Unify); + + /// + /// Checks if a type can be assigned to another. + /// + /// The target type to assign to. + /// The assigned type. + /// True, if the assignment is possible, false otherwise. + public static bool CanAssign(TypeSymbol targetType, TypeSymbol assignedType) => + AssignRecursionScheme(targetType, assignedType, CanUnify); + + /// + /// Recursion scheme for assignment with the unification factored out. + /// This way the assignment can be reused for checks without performing the unification. + /// + /// The target type to assign to. + /// The assigned type. + /// The unification action to perform. + /// True, if the assignment was successful, false otherwise. + private static bool AssignRecursionScheme(TypeSymbol targetType, TypeSymbol assignedType, Func unify) + { + targetType = targetType.Substitution; + assignedType = assignedType.Substitution; + + if (targetType.IsGenericInstance && assignedType.IsGenericInstance) + { + // We need to look for the base type + var targetGenericDefinition = targetType.GenericDefinition!; + + var assignedToUnify = assignedType.BaseTypes + .FirstOrDefault(t => SymbolEqualityComparer.Default.Equals(t.GenericDefinition, targetGenericDefinition)); + if (assignedToUnify is null) + { + // TODO + throw new NotImplementedException(); + } + + // Unify + return unify(targetType, assignedToUnify); + } + else + { + // TODO: Might not be correct + return unify(targetType, assignedType); + } + } + + // Unification ///////////////////////////////////////////////////////////// + + /// + /// Unified a type with the error type. + /// Does not assert the unification success, this is an error-cascading measure. + /// + /// The type to unify with the error type. + public static void UnifyWithError(TypeSymbol type) => Unify(type, WellKnownTypes.ErrorType); + /// /// Unifies two types, asserting their success. /// @@ -27,7 +103,27 @@ public static void UnifyAsserted(TypeSymbol first, TypeSymbol second) /// The first type to unify. /// The second type to unify. /// True, if unification was successful, false otherwise. - public static bool Unify(TypeSymbol first, TypeSymbol second) + public static bool Unify(TypeSymbol first, TypeSymbol second) => + UnifyRecursionScheme(first, second, (tv, type) => tv.Substitute(type)); + + /// + /// Checks if two types can be unified. + /// + /// The first type to unify. + /// The second type to unify. + /// True, if unification is possible, false otherwise. + public static bool CanUnify(TypeSymbol first, TypeSymbol second) => + UnifyRecursionScheme(first, second, (_, _) => { }); + + /// + /// Recursion scheme for unification with the substitution factored out. + /// This way the unification can be reused for checks without performing the substitution. + /// + /// The first type to unify. + /// The second type to unify. + /// The substitution action to perform. + /// True, if unification was successful, false otherwise. + private static bool UnifyRecursionScheme(TypeSymbol first, TypeSymbol second, Action substitute) { first = first.Substitution; second = second.Substitution; @@ -45,17 +141,17 @@ public static bool Unify(TypeSymbol first, TypeSymbol second) // NOTE: Referential equality is OK here, we are checking for CIRCULARITY // which is referential check if (ReferenceEquals(v1, v2)) return true; - v1.Substitute(v2); + substitute(v1, v2); return true; } case (TypeVariable v, TypeSymbol other): { - v.Substitute(other); + substitute(v, other); return true; } case (TypeSymbol other, TypeVariable v): { - v.Substitute(other); + substitute(v, other); return true; } @@ -100,50 +196,4 @@ public static bool Unify(TypeSymbol first, TypeSymbol second) return false; } } - - /// - /// Unified a type with the error type. - /// Does not assert the unification success, this is an error-cascading measure. - /// - /// The type to unify with the error type. - public static void UnifyError(TypeSymbol type) => Unify(type, WellKnownTypes.ErrorType); - - /// - /// Assigns a type to another, asserting their success. - /// - /// The target type to assign to. - /// The assigned type. - public static void AssignAsserted(TypeSymbol targetType, TypeSymbol assignedType) - { - if (Assign(targetType, assignedType)) return; - throw new InvalidOperationException($"could not assign {assignedType} to {targetType}"); - } - - private static bool Assign(TypeSymbol targetType, TypeSymbol assignedType) - { - targetType = targetType.Substitution; - assignedType = assignedType.Substitution; - - if (targetType.IsGenericInstance && assignedType.IsGenericInstance) - { - // We need to look for the base type - var targetGenericDefinition = targetType.GenericDefinition!; - - var assignedToUnify = assignedType.BaseTypes - .FirstOrDefault(t => SymbolEqualityComparer.Default.Equals(t.GenericDefinition, targetGenericDefinition)); - if (assignedToUnify is null) - { - // TODO - throw new NotImplementedException(); - } - - // Unify - return Unify(targetType, assignedToUnify); - } - else - { - // TODO: Might not be correct - return Unify(targetType, assignedType); - } - } } diff --git a/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Rules.cs b/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Rules.cs index 1891ac740..ac288a6fe 100644 --- a/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Rules.cs +++ b/src/Draco.Compiler/Internal/Solver/ConstraintSolver_Rules.cs @@ -69,7 +69,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ common.ReportDiagnostic(diagnostics, builder => builder .WithFormatArgs(string.Join(", ", common.AlternativeTypes))); // Stop cascading uninferred type - UnifyError(common.CommonType); + UnifyWithError(common.CommonType); }) .Named("common_ancestor"), @@ -82,7 +82,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ // Don't propagate type errors if (accessed.IsError) { - UnifyError(member.MemberType); + UnifyWithError(member.MemberType); member.CompletionSource.SetResult(ErrorMemberSymbol.Instance); return; } @@ -103,7 +103,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ .WithFormatArgs(member.MemberName, accessed)); } // We still provide a single error symbol - UnifyError(member.MemberType); + UnifyWithError(member.MemberType); member.CompletionSource.SetResult(ErrorMemberSymbol.Instance); return; } @@ -126,7 +126,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ // TODO: Can this assertion fail? Like in a faulty module decl? // NOTE: Visibility will be checked by the overload constraint Debug.Assert(membersWithName.All(m => m is FunctionSymbol)); - UnifyError(member.MemberType); + UnifyWithError(member.MemberType); var overload = new FunctionGroupSymbol(membersWithName.Cast().ToImmutableArray()); member.CompletionSource.SetResult(overload); } @@ -142,7 +142,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ // Don't propagate type errors if (accessed.IsError) { - UnifyError(indexer.ElementType); + UnifyWithError(indexer.ElementType); // Best-effort shape approximation var errorSymbol = indexer.IsGetter ? ErrorPropertySymbol.CreateIndexerGet(indexer.Indices.Length) @@ -166,7 +166,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ : SymbolResolutionErrors.NoSettableIndexerInType) .WithFormatArgs(accessed)); - UnifyError(indexer.ElementType); + UnifyWithError(indexer.ElementType); // Best-effort shape approximation var errorSymbol = indexer.IsGetter ? ErrorPropertySymbol.CreateIndexerGet(indexer.Indices.Length) @@ -225,7 +225,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ if (called.IsError) { // Don't propagate errors - UnifyError(callable.ReturnType); + UnifyWithError(callable.ReturnType); return; } @@ -236,7 +236,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ if (functionType is null) { // Error - UnifyError(callable.ReturnType); + UnifyWithError(callable.ReturnType); callable.ReportDiagnostic(diagnostics, diag => diag .WithTemplate(TypeCheckingErrors.CallNonFunction) .WithFormatArgs(called)); @@ -300,7 +300,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ if (candidates.Length == 0) { // Could not resolve, error - UnifyError(overload.ReturnType); + UnifyWithError(overload.ReturnType); // Best-effort shape approximation var errorSymbol = new ErrorFunctionSymbol(overload.Candidates.Arguments.Length); overload.CompletionSource.SetResult(errorSymbol); @@ -318,7 +318,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ { // Ambiguity, error // Best-effort shape approximation - UnifyError(overload.ReturnType); + UnifyWithError(overload.ReturnType); var errorSymbol = new ErrorFunctionSymbol(overload.Candidates.Arguments.Length); overload.CompletionSource.SetResult(errorSymbol); // NOTE: If the arguments have an error, we don't report an error here to not cascade errors @@ -399,6 +399,7 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ // As a last-last effort, we assume that a singular assignment means exact matching types Simplification(typeof(Assignable)) + .Guard((Assignable assignable) => CanAssign(assignable.TargetType, assignable.AssignedType)) .Body((ConstraintStore store, Assignable assignable) => AssignAsserted(assignable.TargetType, assignable.AssignedType)) .Named("sole_assignable"), @@ -409,7 +410,8 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ Simplification(typeof(CommonAncestor)) .Guard((CommonAncestor common) => common.AlternativeTypes.Count(t => !t.Substitution.IsTypeVariable) == 1 - && common.AlternativeTypes.Count(t => t.Substitution.IsTypeVariable) == common.AlternativeTypes.Length - 1) + && common.AlternativeTypes.Count(t => t.Substitution.IsTypeVariable) == common.AlternativeTypes.Length - 1 + && common.AlternativeTypes.All(alt => CanUnify(alt, common.CommonType))) .Body((ConstraintStore store, CommonAncestor common) => { var nonTypeVar = common.AlternativeTypes.First(t => !t.Substitution.IsTypeVariable); @@ -421,11 +423,11 @@ private IEnumerable ConstructRules(DiagnosticBag diagnostics) => [ // If the target type of common ancestor is a concrete type, we can try to unify all non-concrete types Simplification(typeof(CommonAncestor)) - .Guard((CommonAncestor common) => common.CommonType.Substitution.IsGroundType) + .Guard((CommonAncestor common) => common.CommonType.Substitution.IsGroundType + && common.AlternativeTypes.All(alt => CanUnify(alt, common.CommonType))) .Body((ConstraintStore store, CommonAncestor common) => { var concreteType = common.CommonType.Substitution; - // TODO: Can we do this asserted? foreach (var type in common.AlternativeTypes) UnifyAsserted(type, concreteType); }) .Named("concrete_common_ancestor"),