Better context info in Type_Error raised from return type checks (#8566)

- Followup to #8502 that adds better error messages
This commit is contained in:
Radosław Waśko 2023-12-20 19:22:47 +01:00 committed by GitHub
parent d56b800c11
commit dfdb547616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 115 additions and 89 deletions

View File

@ -59,8 +59,9 @@ type Type_Error
Arguments:
- expected: The expected type at the error location.
- actual: The actual type at the error location.
- name: The name of the argument whose type is mismatched.
Error expected actual name
- comment: Description of the value that was being checked,
e.g. function argument, result or an arbitrary expression.
Error expected actual comment
## PRIVATE
Convert the Type_Error error to a human-readable format.
@ -78,7 +79,7 @@ type Type_Error
_ -> ". Try to apply " + (missing_args.join ", ") + " arguments"
_ -> tpe.to_text
"Type error: expected `"+self.name+"` to be "+self.expected.to_display_text+", but got "+type_of_actual+"."
"Type error: expected "+self.comment+" to be "+self.expected.to_display_text+", but got "+type_of_actual+"."
@Builtin_Type
type Compile_Error

View File

@ -30,7 +30,7 @@ import scala.Option;
@Persistable(clazz = GatherDiagnostics.DiagnosticsMeta.class, id = 1114)
@Persistable(clazz = DocumentationComments.Doc.class, id = 1115)
@Persistable(clazz = AliasAnalysis$Info$Occurrence.class, id = 1116)
@Persistable(clazz = TypeSignatures.Signature.class, id = 1117)
@Persistable(clazz = TypeSignatures.Signature.class, id = 2117)
@Persistable(clazz = ModuleAnnotations.Annotations.class, id = 1118)
@Persistable(clazz = AliasAnalysis$Info$Scope$Root.class, id = 1120)
@Persistable(clazz = DataflowAnalysis$DependencyInfo$Type$Static.class, id = 1121)

View File

@ -488,7 +488,7 @@ final class SuggestionBuilder[A: IndexedSource](
typeSignature: Option[TypeSignatures.Metadata]
): Vector[TypeArg] =
typeSignature match {
case Some(TypeSignatures.Signature(typeExpr)) =>
case Some(TypeSignatures.Signature(typeExpr, _)) =>
buildTypeSignature(typeExpr)
case _ =>
Vector()

View File

@ -393,7 +393,7 @@ case object DataflowAnalysis extends IRPass {
*/
def analyseType(typ: Type, info: DependencyInfo): Type = {
typ match {
case asc @ Type.Ascription(typed, signature, _, _, _) =>
case asc @ Type.Ascription(typed, signature, _, _, _, _) =>
val ascrDep = asStatic(asc)
val typedDep = asStatic(typed)
val sigDep = asStatic(signature)

View File

@ -166,7 +166,7 @@ case object ComplexType extends IRPass {
): List[Definition] = {
var unusedSig: Option[Type.Ascription] = None
val sig = lastSignature match {
case Some(Type.Ascription(typed, _, _, _, _)) =>
case Some(Type.Ascription(typed, _, _, _, _, _)) =>
typed match {
case literal: Name.Literal =>
if (name.name == literal.name) {

View File

@ -115,7 +115,7 @@ case object SuspendedArguments extends IRPass {
method.body match {
case lam @ Function.Lambda(args, body, _, _, _, _) =>
method.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
case Some(Signature(signature, _)) =>
val newArgs = computeSuspensions(args.drop(1), signature)
if (newArgs.head.suspended) {
errors.Conversion(
@ -163,7 +163,7 @@ case object SuspendedArguments extends IRPass {
body match {
case lam @ Function.Lambda(args, lamBody, _, _, _, _) =>
explicit.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
case Some(Signature(signature, _)) =>
val newArgs = computeSuspensions(
args.drop(1),
signature
@ -214,7 +214,7 @@ case object SuspendedArguments extends IRPass {
expression.transformExpressions {
case bind @ Expression.Binding(_, expr, _, _, _) =>
val newExpr = bind.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
case Some(Signature(signature, _)) =>
expr match {
case lam @ Function.Lambda(args, body, _, _, _, _) =>
lam.copy(
@ -229,7 +229,7 @@ case object SuspendedArguments extends IRPass {
bind.copy(expression = newExpr)
case lam @ Function.Lambda(args, body, _, _, _, _) =>
lam.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
case Some(Signature(signature, _)) =>
lam.copy(
arguments = computeSuspensions(args, signature),
body = resolveExpression(body)

View File

@ -186,7 +186,7 @@ case object TypeFunctions extends IRPass {
name.name match {
case Type.Ascription.name =>
Type.Ascription(leftArg, rightArg, location)
Type.Ascription(leftArg, rightArg, None, location)
case Type.Context.name =>
Type.Context(leftArg, rightArg, location)
case Type.Error.name =>

View File

@ -111,7 +111,8 @@ case object TypeNames extends IRPass {
new MetadataPair(
TypeSignatures,
TypeSignatures.Signature(
resolveSignature(typeParams, bindingsMap, s.signature)
resolveSignature(typeParams, bindingsMap, s.signature),
s.comment
)
)
)

View File

@ -97,7 +97,7 @@ case object TypeSignatures extends IRPass {
case _ =>
}
val res = lastSignature match {
case Some(asc @ Type.Ascription(typed, sig, _, _, _)) =>
case Some(asc @ Type.Ascription(typed, sig, comment, _, _, _)) =>
val methodRef = meth.methodReference
val newMethodWithDoc = asc
.getMetadata(DocumentationComments)
@ -121,7 +121,7 @@ case object TypeSignatures extends IRPass {
if (ref isSameReferenceAs methodRef) {
Some(
newMethodWithAnnotations.updateMetadata(
new MetadataPair(this, Signature(sig))
new MetadataPair(this, Signature(sig, comment))
)
)
} else {
@ -246,7 +246,9 @@ case object TypeSignatures extends IRPass {
private def resolveAscription(sig: Type.Ascription): Expression = {
val newTyped = sig.typed.mapExpressions(resolveExpression)
val newSig = sig.signature.mapExpressions(resolveExpression)
newTyped.updateMetadata(new MetadataPair(this, Signature(newSig)))
newTyped.updateMetadata(
new MetadataPair(this, Signature(newSig, sig.comment))
)
}
/** Resolves type signatures in a block.
@ -271,7 +273,7 @@ case object TypeSignatures extends IRPass {
case binding: Expression.Binding =>
val newBinding = binding.mapExpressions(resolveExpression)
val res = lastSignature match {
case Some(asc @ Type.Ascription(typed, sig, _, _, _)) =>
case Some(asc @ Type.Ascription(typed, sig, comment, _, _, _)) =>
val name = binding.name
val newBindingWithDoc = asc
.getMetadata(DocumentationComments)
@ -287,7 +289,7 @@ case object TypeSignatures extends IRPass {
if (typedName.name == name.name) {
Some(
newBindingWithDoc.updateMetadata(
new MetadataPair(this, Signature(sig))
new MetadataPair(this, Signature(sig, comment))
)
)
} else {
@ -321,8 +323,10 @@ case object TypeSignatures extends IRPass {
/** A representation of a type signature.
*
* @param signature the expression for the type signature
* @param comment an optional comment from which the potential error message will be derived
*/
case class Signature(signature: Expression) extends IRPass.IRMetadata {
case class Signature(signature: Expression, comment: Option[String] = None)
extends IRPass.IRMetadata {
override val metadataName: String = "TypeSignatures.Signature"
/** @inheritdoc */
@ -345,6 +349,6 @@ case object TypeSignatures extends IRPass {
/** @inheritdoc */
override def duplicate(): Option[IRPass.IRMetadata] =
Some(this.copy(signature = signature.duplicate()))
Some(this.copy(signature = signature.duplicate(), comment = comment))
}
}

View File

@ -96,7 +96,7 @@ final class TreeToIr {
methodReference = translateExpression(sig.getVariable());
}
var signature = translateType(sig.getType());
var ascription = new Type.Ascription(methodReference, signature, getIdentifiedLocation(sig), meta(), diag());
var ascription = new Type.Ascription(methodReference, signature, Option.empty(), getIdentifiedLocation(sig), meta(), diag());
yield ascription;
}
default -> translateExpression(exprTree);
@ -250,7 +250,8 @@ final class TreeToIr {
yield join(error, appendTo);
}
var ascribedBody = addTypeAscription(body, returnSignature, loc);
String functionName = fn.getName().codeRepr();
var ascribedBody = addTypeAscription(functionName, body, returnSignature, loc);
var binding = new Method.Binding(
methodRef,
args,
@ -317,7 +318,7 @@ final class TreeToIr {
case Tree.TypeSignature sig -> {
var methodReference = translateMethodReference(sig.getVariable(), true);
var signature = translateType(sig.getType());
var ascription = new Type.Ascription(methodReference, signature, getIdentifiedLocation(sig), meta(), diag());
var ascription = new Type.Ascription(methodReference, signature, Option.empty(), getIdentifiedLocation(sig), meta(), diag());
yield join(ascription, appendTo);
}
@ -507,6 +508,7 @@ final class TreeToIr {
var loc = getIdentifiedLocation(fun);
var body = translateExpression(treeBody);
String functionName = name.name();
if (args.isEmpty()) {
if (body instanceof Expression.Block block) {
// suspended block has a name and no arguments
@ -524,14 +526,14 @@ final class TreeToIr {
body = translateSyntaxError(fun, Syntax.UnexpectedExpression$.MODULE$);
}
var ascribedBody = addTypeAscription(body, returnType, loc);
var ascribedBody = addTypeAscription(functionName, body, returnType, loc);
return new Expression.Binding(name, ascribedBody, loc, meta(), diag());
} else {
if (body == null) {
return translateSyntaxError(fun, Syntax.UnexpectedDeclarationInType$.MODULE$);
}
var ascribedBody = addTypeAscription(body, returnType, loc);
var ascribedBody = addTypeAscription(functionName, body, returnType, loc);
return new Function.Binding(name, args, ascribedBody, loc, true, meta(), diag());
}
}
@ -551,17 +553,18 @@ final class TreeToIr {
* <p>
* If the type is {@code null}, the body is returned unchanged.
*/
private Expression addTypeAscription(Expression body, Expression type, Option<IdentifiedLocation> loc) {
private Expression addTypeAscription(String functionName, Expression body, Expression type, Option<IdentifiedLocation> loc) {
if (type == null) {
return body;
}
return new Type.Ascription(body, type, loc, meta(), diag());
String comment = "the result of `" + functionName + "`";
return new Type.Ascription(body, type, Option.apply(comment), loc, meta(), diag());
}
private Type.Ascription translateTypeSignature(Tree sig, Tree type, Expression typeName) {
var fn = translateType(type);
return new Type.Ascription(typeName, fn, getIdentifiedLocation(sig), meta(), diag());
return new Type.Ascription(typeName, fn, Option.empty(), getIdentifiedLocation(sig), meta(), diag());
}

View File

@ -122,6 +122,7 @@ object Type {
*
* @param typed the expression being ascribed a type
* @param signature the signature being ascribed to `typed`
* @param comment a comment that may be used to add context to the type error
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
@ -129,6 +130,7 @@ object Type {
sealed case class Ascription(
typed: Expression,
signature: Expression,
comment: Option[String],
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = new MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
@ -141,6 +143,7 @@ object Type {
*
* @param typed the expression being ascribed a type
* @param signature the signature being ascribed to `typed`
* @param comment a comment that may be used to add context to the type error
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
@ -150,12 +153,14 @@ object Type {
def copy(
typed: Expression = typed,
signature: Expression = signature,
comment: Option[String] = comment,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: UUID @Identifier = id
): Ascription = {
val res = Ascription(typed, signature, location, passData, diagnostics)
val res =
Ascription(typed, signature, comment, location, passData, diagnostics)
res.id = id
res
}
@ -205,6 +210,7 @@ object Type {
s"""Type.Ascription(
|typed = $typed,
|signature = $signature,
|comment = $comment,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,

View File

@ -19,7 +19,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.enso.compiler.core.ir.Name;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.node.BaseNode.TailStatus;
import org.enso.interpreter.node.EnsoRootNode;
@ -49,11 +48,11 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.graalvm.collections.Pair;
public abstract class ReadArgumentCheckNode extends Node {
private final String name;
private final String comment;
@CompilerDirectives.CompilationFinal private String expectedTypeMessage;
ReadArgumentCheckNode(String name) {
this.name = name;
ReadArgumentCheckNode(String comment) {
this.comment = comment;
}
/** */
@ -62,7 +61,7 @@ public abstract class ReadArgumentCheckNode extends Node {
}
/**
* Executes check or conversion of the value.abstract
* Executes check or conversion of the value.
*
* @param frame frame requesting the conversion
* @param value the value to convert
@ -89,12 +88,12 @@ public abstract class ReadArgumentCheckNode extends Node {
expectedTypeMessage = expectedTypeMessage();
}
var ctx = EnsoContext.get(this);
var msg = name == null ? "expression" : name;
var err = ctx.getBuiltins().error().makeTypeError(expectedTypeMessage, v, msg);
var msg = comment == null ? "expression" : comment;
var err = ctx.getBuiltins().error().makeTypeErrorOfComment(expectedTypeMessage, v, msg);
throw new PanicException(err, this);
}
public static ReadArgumentCheckNode allOf(Name argumentName, ReadArgumentCheckNode... checks) {
public static ReadArgumentCheckNode allOf(String argumentName, ReadArgumentCheckNode... checks) {
var list = Arrays.asList(checks);
var flatten =
list.stream()
@ -105,27 +104,25 @@ public abstract class ReadArgumentCheckNode extends Node {
return switch (arr.length) {
case 0 -> null;
case 1 -> arr[0];
default -> new AllOfNode(argumentName.name(), arr);
default -> new AllOfNode(argumentName, arr);
};
}
public static ReadArgumentCheckNode oneOf(Name argumentName, List<ReadArgumentCheckNode> checks) {
public static ReadArgumentCheckNode oneOf(String comment, List<ReadArgumentCheckNode> checks) {
var arr = toArray(checks);
return switch (arr.length) {
case 0 -> null;
case 1 -> arr[0];
default -> new OneOfNode(argumentName.name(), arr);
default -> new OneOfNode(comment, arr);
};
}
public static ReadArgumentCheckNode build(Name argumentName, Type expectedType) {
var n = argumentName == null ? null : argumentName.name();
return ReadArgumentCheckNodeFactory.TypeCheckNodeGen.create(n, expectedType);
public static ReadArgumentCheckNode build(String comment, Type expectedType) {
return ReadArgumentCheckNodeFactory.TypeCheckNodeGen.create(comment, expectedType);
}
public static ReadArgumentCheckNode meta(Name argumentName, Object metaObject) {
var n = argumentName == null ? null : argumentName.name();
return ReadArgumentCheckNodeFactory.MetaCheckNodeGen.create(n, metaObject);
public static ReadArgumentCheckNode meta(String comment, Object metaObject) {
return ReadArgumentCheckNodeFactory.MetaCheckNodeGen.create(comment, metaObject);
}
public static boolean isWrappedThunk(Function fn) {

View File

@ -13,6 +13,6 @@ public class TypeError extends UniquelyConstructibleBuiltin {
@Override
protected List<String> getConstructorParamNames() {
return List.of("expected", "actual", "name");
return List.of("expected", "actual", "comment");
}
}

View File

@ -176,11 +176,24 @@ public final class Error {
*
* @param expected the expected type
* @param actual the actual type
* @param name the name of the variable that is a type error
* @param name name of the argument that was being checked
* @return a runtime representation of the error.
*/
@CompilerDirectives.TruffleBoundary
public Atom makeTypeError(Object expected, Object actual, String name) {
return typeError.newInstance(expected, actual, Text.create(name));
return typeError.newInstance(expected, actual, Text.create("`" + name + "`"));
}
/**
* Creates an instance of the runtime representation of a {@code Type_Error}.
*
* @param expected the expected type
* @param actual the actual type
* @param comment description of the value that was being checked
* @return a runtime representation of the error.
*/
public Atom makeTypeErrorOfComment(Object expected, Object actual, String comment) {
return typeError.newInstance(expected, actual, Text.create(comment));
}
/**

View File

@ -713,24 +713,24 @@ class IrToTruffle(
// ==========================================================================
private def extractAscribedType(
name: Name,
comment: String,
t: Expression
): ReadArgumentCheckNode = t match {
case u: `type`.Set.Union =>
ReadArgumentCheckNode.oneOf(
name,
u.operands.map(extractAscribedType(name, _)).asJava
comment,
u.operands.map(extractAscribedType(comment, _)).asJava
)
case i: `type`.Set.Intersection =>
ReadArgumentCheckNode.allOf(
name,
extractAscribedType(name, i.left),
extractAscribedType(name, i.right)
comment,
extractAscribedType(comment, i.left),
extractAscribedType(comment, i.right)
)
case p: Application.Prefix => extractAscribedType(name, p.function)
case p: Application.Prefix => extractAscribedType(comment, p.function)
case _: Tpe.Function =>
ReadArgumentCheckNode.build(
name,
comment,
context.getTopScope().getBuiltins().function()
)
case t => {
@ -740,7 +740,7 @@ class IrToTruffle(
.Resolution(BindingsMap.ResolvedType(mod, tpe))
) =>
ReadArgumentCheckNode.build(
name,
comment,
asScope(
mod
.unsafeAsModule()
@ -753,7 +753,7 @@ class IrToTruffle(
.Resolution(BindingsMap.ResolvedPolyglotSymbol(mod, symbol))
) =>
ReadArgumentCheckNode.meta(
name,
comment,
asScope(
mod
.unsafeAsModule()
@ -768,7 +768,8 @@ class IrToTruffle(
private def checkAsTypes(
arg: DefinitionArgument
): ReadArgumentCheckNode = {
arg.ascribedType.map(extractAscribedType(arg.name, _)).getOrElse(null)
val comment = "`" + arg.name.name + "`"
arg.ascribedType.map(extractAscribedType(comment, _)).getOrElse(null)
}
/** Checks if the expression has a @Builtin_Method annotation
@ -1074,9 +1075,11 @@ class IrToTruffle(
ir match {
case _: Expression.Binding =>
case _ =>
val types = ir.getMetadata(TypeSignatures)
val types: Option[TypeSignatures.Signature] =
ir.getMetadata(TypeSignatures)
types.foreach { tpe =>
val checkNode = extractAscribedType(null, tpe.signature);
val checkNode =
extractAscribedType(tpe.comment.orNull, tpe.signature);
if (checkNode != null) {
runtimeExpression =
ReadArgumentCheckNode.wrap(runtimeExpression, checkNode)

View File

@ -66,7 +66,8 @@ public class SignaturePolyglotTest extends TestBase {
var ret = fn.execute(new StringBuilder("Hi"));
fail("Should fail, but got: " + ret);
} catch (PolyglotException e) {
assertTypeError("`x`", "java.time.format.DateTimeFormatter", "StringBuilder", e.getMessage());
assertTypeError(
"`x`", "java.time.format.DateTimeFormatter", "java.lang.StringBuilder", e.getMessage());
}
}

View File

@ -506,7 +506,7 @@ public class SignatureTest extends TestBase {
var v = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "Bin.Zero One");
fail("Expecting an error, not " + v);
} catch (PolyglotException ex) {
assertTypeError("`v`", "Zero", "Zero", ex.getMessage());
assertTypeError("`v`", "Zero", "One", ex.getMessage());
}
}
@ -639,7 +639,7 @@ public class SignatureTest extends TestBase {
var v = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "Bin.Vec 'Hi'");
fail("Expecting an error, not " + v);
} catch (PolyglotException ex) {
assertTypeError("`v`", "Integer | Range | Vector", "Integer", ex.getMessage());
assertTypeError("`v`", "Integer | Range | Vector", "Text", ex.getMessage());
}
var ok2 = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "Bin.Either Zero");
assertEquals("binary.Bin", ok2.getMetaObject().getMetaQualifiedName());
@ -872,7 +872,8 @@ public class SignatureTest extends TestBase {
var res = plusChecked.execute("a", "b");
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains(
"expected the result of `plusChecked` to be Integer, but got Text", e.getMessage());
}
}
@ -923,7 +924,8 @@ public class SignatureTest extends TestBase {
var res = plusChecked.execute(2, 3);
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains(
"expected the result of `constant` to be Integer, but got Text", e.getMessage());
}
}
@ -944,13 +946,13 @@ public class SignatureTest extends TestBase {
.buildLiteral();
var module = ctx.eval(src);
var plusChecked = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "foo");
assertEquals(8, plusChecked.execute(2).asInt());
var foo = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "foo");
assertEquals(8, foo.execute(2).asInt());
try {
var res = plusChecked.execute(".");
var res = foo.execute(".");
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains("expected the result of `x` to be Integer, but got Text", e.getMessage());
}
}
@ -978,7 +980,7 @@ public class SignatureTest extends TestBase {
var res = plusChecked.execute(".");
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains("expected the result of `x` to be Integer, but got Text", e.getMessage());
}
}
@ -1008,7 +1010,7 @@ public class SignatureTest extends TestBase {
var res = foo.execute(2);
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains("expected the result of `foo` to be Integer, but got Text", e.getMessage());
}
var res = foo.execute(3);
@ -1043,8 +1045,7 @@ public class SignatureTest extends TestBase {
var res = factorial.execute(20);
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
// TODO we may want to change `expression` to 'the return type' or something
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains("expected the result of `go` to be Integer, but got Text", e.getMessage());
}
}
@ -1080,7 +1081,7 @@ public class SignatureTest extends TestBase {
var res = foo.execute(n, 1);
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertContains("expected the result of `go` to be Integer, but got Text", e.getMessage());
}
}
@ -1113,20 +1114,16 @@ public class SignatureTest extends TestBase {
var res = foo_bad.execute(n);
fail("Expecting an exception, not: " + res);
} catch (PolyglotException e) {
assertContains("expected `expression` to be Integer, but got Text", e.getMessage());
assertEquals(
"Type error: expected the result of `foo_bad` to be Integer, but got Text.",
e.getMessage());
}
}
static void assertTypeError(String expArg, String expType, String realType, String msg) {
if (!msg.contains(expArg)) {
fail("Expecting value " + expArg + " in " + msg);
}
if (!msg.contains(expType)) {
fail("Expecting value " + expType + " in " + msg);
}
if (!msg.contains(realType)) {
fail("Expecting value " + realType + " in " + msg);
}
assertEquals(
"Type error: expected " + expArg + " to be " + expType + ", but got " + realType + ".",
msg);
}
private static void assertContains(String exp, String msg) {

View File

@ -117,7 +117,7 @@ class TextTest extends InterpreterTest {
|main =
| IO.println (List.Cons Nothing Nothing).to_display_text
| IO.println (Syntax_Error.Error "foo").to_display_text
| IO.println (Type_Error.Error Nothing List.Nil "myvar").to_display_text
| IO.println (Type_Error.Error Nothing List.Nil "`myvar`").to_display_text
| IO.println (Compile_Error.Error "error :(").to_display_text
| IO.println (Inexhaustive_Pattern_Match.Error 32).to_display_text
| IO.println (Arithmetic_Error.Error "cannot frobnicate quaternions").to_display_text

View File

@ -17,13 +17,13 @@ type Arithmetic_Error
@Builtin_Type
type Type_Error
Error expected actual name
Error expected actual comment
type_of_actual self =
tpe = Meta.type_of self.actual
if tpe.is_error then self.actual.to_display_text else tpe.to_display_text
to_display_text self = "Type error: expected `"+self.name+"` to be "+self.expected.to_display_text+", but got "+self.type_of_actual+"."
to_display_text self = "Type error: expected "+self.comment+" to be "+self.expected.to_display_text+", but got "+self.type_of_actual+"."
@Builtin_Type
type Not_Invokable