Skip to content

Commit

Permalink
Verify ascribed types of parameters really exist (#6584)
Browse files Browse the repository at this point in the history
Verify ascribed types `(a : Xyz)` are checked for existence.
  • Loading branch information
JaroslavTulach authored May 14, 2023
1 parent 7067917 commit 41bb52c
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import project.Data.Numbers.Integer
import project.Data.Range.Extensions
import project.Data.Range.Range
import project.Data.Text.Regex.No_Such_Group
import project.Data.Text.Regex.Pattern.Pattern
import project.Data.Text.Span.Span
import project.Data.Text.Span.Utf_16_Span
import project.Data.Text.Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Standard.Table.Internal.Widget_Helpers
from Standard.Table import Sort_Column, Data_Formatter, Value_Type, Auto
from Standard.Table.Errors import Floating_Point_Equality, Inexact_Type_Coercion, Invalid_Value_Type, Lossy_Conversion

import project.Connection.Connection.Connection
import project.Data.SQL_Statement.SQL_Statement
import project.Data.SQL_Type.SQL_Type
import project.Internal.Helpers
Expand Down
7 changes: 5 additions & 2 deletions engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ yield switch (op.codeRepr()) {
}
case Tree.Ident id when insideTypeAscription -> {
try {
yield buildQualifiedName(id, getIdentifiedLocation(id), false);
yield buildNameOrQualifiedName(id, getIdentifiedLocation(id));
} catch (SyntaxException ex) {
yield ex.toError();
}
Expand Down Expand Up @@ -1412,11 +1412,14 @@ private List<IR.Pattern> translatePatternFields(java.util.List<Tree> tail) throw
return new IR$Name$Qualified(qualifiedNameSegments(t, generateId), loc, meta(), diag());
}
private IR.Name buildNameOrQualifiedName(Tree t) throws SyntaxException {
return buildNameOrQualifiedName(t, Option.empty());
}
private IR.Name buildNameOrQualifiedName(Tree t, Option<IdentifiedLocation> loc) throws SyntaxException {
var segments = qualifiedNameSegments(t, false);
if (segments.length() == 1) {
return segments.head();
} else {
return new IR$Name$Qualified(segments, Option.empty(), meta(), diag());
return new IR$Name$Qualified(segments, loc, meta(), diag());
}
}
private java.util.List<Tree> unrollOprRhs(Tree list, String operator) throws SyntaxException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import scala.annotation.unused
*/

@SerialVersionUID(
5569L // removes special handling of `enso_project` method
6584L // verify ascribed types
)
case class BindingsMap(
definedEntities: List[DefinedEntity],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ case object GlobalNames extends IRPass {
"No fresh name supply passed to UppercaseNames resolver."
)
)
processExpression(ir, scopeMap, freshNameSupply, None)
processExpression(ir, scopeMap, List(), freshNameSupply, None)
}

/** @inheritdoc */
Expand All @@ -110,7 +110,7 @@ case object GlobalNames extends IRPass {
_.getMetadata(MethodDefinitions)
)
method.mapExpressions(
processExpression(_, bindings, freshNameSupply, resolution)
processExpression(_, bindings, List(), freshNameSupply, resolution)
)
case tp: IR.Module.Scope.Definition.Type =>
tp.copy(members =
Expand All @@ -119,6 +119,7 @@ case object GlobalNames extends IRPass {
processExpression(
_,
bindings,
tp.params,
freshNameSupply,
bindings.resolveName(tp.name.name).toOption.map(Resolution)
)
Expand All @@ -127,13 +128,16 @@ case object GlobalNames extends IRPass {
)

case a =>
a.mapExpressions(processExpression(_, bindings, freshNameSupply, None))
a.mapExpressions(
processExpression(_, bindings, List(), freshNameSupply, None)
)
}
}

private def processExpression(
ir: IR.Expression,
bindings: BindingsMap,
params: List[IR.DefinitionArgument],
freshNameSupply: FreshNameSupply,
selfTypeResolution: Option[Resolution],
isInsideApplication: Boolean = false
Expand All @@ -149,62 +153,66 @@ case object GlobalNames extends IRPass {
)
)
case lit: IR.Name.Literal =>
lit.getMetadata(FullyQualifiedNames) match {
case Some(
FullyQualifiedNames.FQNResolution(
FullyQualifiedNames.ResolvedModule(modRef)
)
) =>
lit.updateMetadata(this -->> Resolution(ResolvedModule(modRef)))
case _ =>
if (!lit.isMethod && !isLocalVar(lit)) {
val resolution = bindings.resolveName(lit.name)
resolution match {
case Left(error) =>
IR.Error.Resolution(
lit,
IR.Error.Resolution.ResolverError(error)
if (params.exists(p => p.name.name == lit.name)) {
lit
} else {
lit.getMetadata(FullyQualifiedNames) match {
case Some(
FullyQualifiedNames.FQNResolution(
FullyQualifiedNames.ResolvedModule(modRef)
)
case Right(r @ BindingsMap.ResolvedMethod(mod, method)) =>
if (isInsideApplication) {
lit.updateMetadata(this -->> BindingsMap.Resolution(r))
} else {
val self = freshNameSupply
.newName()
.updateMetadata(
this -->> BindingsMap.Resolution(
BindingsMap.ResolvedModule(mod)
)
)
// The synthetic applications gets the location so that instrumentation
// identifies the node correctly
val fun = lit.copy(
name = method.name,
location = None
)
val app = IR.Application.Prefix(
fun,
List(IR.CallArgument.Specified(None, self, None)),
hasDefaultsSuspended = false,
lit.location
) =>
lit.updateMetadata(this -->> Resolution(ResolvedModule(modRef)))
case _ =>
if (!lit.isMethod && !isLocalVar(lit)) {
val resolution = bindings.resolveName(lit.name)
resolution match {
case Left(error) =>
IR.Error.Resolution(
lit,
IR.Error.Resolution.ResolverError(error)
)
fun
.getMetadata(ExpressionAnnotations)
.foreach(annotationsMeta =>
app.updateMetadata(
ExpressionAnnotations -->> annotationsMeta
case Right(r @ BindingsMap.ResolvedMethod(mod, method)) =>
if (isInsideApplication) {
lit.updateMetadata(this -->> BindingsMap.Resolution(r))
} else {
val self = freshNameSupply
.newName()
.updateMetadata(
this -->> BindingsMap.Resolution(
BindingsMap.ResolvedModule(mod)
)
)
// The synthetic applications gets the location so that instrumentation
// identifies the node correctly
val fun = lit.copy(
name = method.name,
location = None
)
fun.passData.remove(ExpressionAnnotations)
app
}
case Right(value) =>
lit.updateMetadata(this -->> BindingsMap.Resolution(value))
}
val app = IR.Application.Prefix(
fun,
List(IR.CallArgument.Specified(None, self, None)),
hasDefaultsSuspended = false,
lit.location
)
fun
.getMetadata(ExpressionAnnotations)
.foreach(annotationsMeta =>
app.updateMetadata(
ExpressionAnnotations -->> annotationsMeta
)
)
fun.passData.remove(ExpressionAnnotations)
app
}
case Right(value) =>
lit.updateMetadata(this -->> BindingsMap.Resolution(value))
}

} else {
lit
}
} else {
lit
}
}
}
case app: IR.Application.Prefix =>
app.function match {
Expand All @@ -214,13 +222,15 @@ case object GlobalNames extends IRPass {
app,
lit,
bindings,
params,
freshNameSupply,
selfTypeResolution
)
else
resolveLocalApplication(
app,
bindings,
params,
freshNameSupply,
selfTypeResolution
)
Expand All @@ -229,6 +239,7 @@ case object GlobalNames extends IRPass {
processExpression(
_,
bindings,
params,
freshNameSupply,
selfTypeResolution
)
Expand All @@ -243,19 +254,27 @@ case object GlobalNames extends IRPass {
app: IR.Application.Prefix,
fun: IR.Name.Literal,
bindingsMap: BindingsMap,
params: List[IR.DefinitionArgument],
freshNameSupply: FreshNameSupply,
selfTypeResolution: Option[Resolution]
): IR.Expression = {
val processedFun = processExpression(
app.function,
bindingsMap,
params,
freshNameSupply,
selfTypeResolution,
isInsideApplication = true
)
val processedArgs = app.arguments.map(
_.mapExpressions(
processExpression(_, bindingsMap, freshNameSupply, selfTypeResolution)
processExpression(
_,
bindingsMap,
params,
freshNameSupply,
selfTypeResolution
)
)
)
processedFun.getMetadata(this) match {
Expand All @@ -278,20 +297,28 @@ case object GlobalNames extends IRPass {
private def resolveLocalApplication(
app: IR.Application.Prefix,
bindings: BindingsMap,
params: List[IR.DefinitionArgument],
freshNameSupply: FreshNameSupply,
selfTypeResolution: Option[Resolution]
): IR.Expression = {
val processedFun =
processExpression(
app.function,
bindings,
params,
freshNameSupply,
selfTypeResolution
)
val processedArgs =
app.arguments.map(
_.mapExpressions(
processExpression(_, bindings, freshNameSupply, selfTypeResolution)
processExpression(
_,
bindings,
params,
freshNameSupply,
selfTypeResolution
)
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.BeforeClass;
Expand Down Expand Up @@ -42,4 +43,59 @@ public void wrongFunctionSignature() throws Exception {
assertTrue("It is a syntax error exception", e.isSyntaxError());
}
}

@Test
public void wrongAscribedTypeSignature() throws Exception {
final URI uri = new URI("memory://neg.enso");
final Source src = Source.newBuilder("enso", """
neg (a : Xyz) = 0 - a
""", uri.getHost())
.uri(uri)
.buildLiteral();

try {
var module = ctx.eval(src);
var neg = module.invokeMember("eval_expression", "neg");
fail("Expecting an exception from compilation, not: " + neg);
} catch (PolyglotException e) {
assertTrue("It is a syntax error exception", e.isSyntaxError());
}
}

@Test
public void wrongAscribedInConstructor() throws Exception {
final URI uri = new URI("memory://constructor.enso");
final Source src = Source.newBuilder("enso", """
type Neg
Val (a : Xyz)
neg = Neg.Val 10
""", uri.getHost())
.uri(uri)
.buildLiteral();

try {
var module = ctx.eval(src);
var neg = module.invokeMember("eval_expression", "neg");
fail("Expecting an exception from compilation, not: " + neg);
} catch (PolyglotException e) {
assertTrue("It is a syntax error exception", e.isSyntaxError());
}
}

@Test
public void ascribedWithAParameter() throws Exception {
final URI uri = new URI("memory://constructor.enso");
final Source src = Source.newBuilder("enso", """
type Maybe a
Nothing
Some unwrap:a
""", uri.getHost())
.uri(uri)
.buildLiteral();

var module = ctx.eval(src);
var some = module.invokeMember("eval_expression", "Maybe.Some 10");
assertEquals("Can read ten", 10, some.getMember("unwrap").asInt());
}
}

0 comments on commit 41bb52c

Please sign in to comment.