diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eb95ff67f1..af322b71b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -525,6 +525,7 @@ - [Introducing Meta.atom_with_hole][4023] - [Report failures in name resolution in type signatures][4030] - [Attach visualizations to sub-expressions][4048] +- [Add Meta.get_annotation method][4049] - [Resolve Fully Qualified Names][4056] - [Optimize Atom storage layouts][3862] @@ -613,6 +614,7 @@ [4023]: https://github.com/enso-org/enso/pull/4023 [4030]: https://github.com/enso-org/enso/pull/4030 [4048]: https://github.com/enso-org/enso/pull/4048 +[4056]: https://github.com/enso-org/enso/pull/4049 [4056]: https://github.com/enso-org/enso/pull/4056 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/build.sbt b/build.sbt index c9416b638b2..51f951cc7ce 100644 --- a/build.sbt +++ b/build.sbt @@ -1146,9 +1146,6 @@ lazy val `polyglot-api` = project "org.scalacheck" %% "scalacheck" % scalacheckVersion % Test ), libraryDependencies ++= jackson, - addCompilerPlugin( - "org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full - ), GenerateFlatbuffers.flatcVersion := flatbuffersVersion, Compile / sourceGenerators += GenerateFlatbuffers.task ) @@ -1443,9 +1440,6 @@ lazy val runtime = (project in file("engine/runtime")) "-s", (Compile / sourceManaged).value.getAbsolutePath, "-Xlint:unchecked" - ), - addCompilerPlugin( - "org.typelevel" %% "kind-projector" % kindProjectorVersion cross CrossVersion.full ) ) .settings( diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 4a6652e27d6..a5fbb6765c2 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -10,6 +10,7 @@ import project.Data.Time.Duration.Duration import project.Data.Time.Time_Of_Day.Time_Of_Day import project.Data.Time.Time_Zone.Time_Zone import project.Data.Vector.Vector +import project.Nothing.Nothing import project.Polyglot.Java import project.Error.Error as Base_Error @@ -333,10 +334,22 @@ java_instance_check value typ = Returns the type of the given value. Arguments: - - value: the value to get the type of. + - value: The value to get the type of. type_of : Any -> Any type_of value = @Builtin_Method "Meta.type_of" +## UNSTABLE + ADVANCED + + Given a type object, method name and a parameter name, return the associated annotation if it exists. + + Arguments: + - target: The value or type to get the attribute from. + - method_name: The name of the method or constructor to get the attribute for. + - parameter_name: The name of the parameter to get the attribute for. +get_annotation : Any -> Text -> Text -> Any | Nothing +get_annotation target method_name parameter_name = @Builtin_Method "Meta.get_annotation" + ## Represents a polyglot language. type Language diff --git a/engine/runtime/src/main/java/org/enso/compiler/EnsoCompiler.java b/engine/runtime/src/main/java/org/enso/compiler/EnsoCompiler.java index 3d561d7666d..81f6f7c31fa 100644 --- a/engine/runtime/src/main/java/org/enso/compiler/EnsoCompiler.java +++ b/engine/runtime/src/main/java/org/enso/compiler/EnsoCompiler.java @@ -26,6 +26,11 @@ public final class EnsoCompiler implements AutoCloseable { } } + public IR.Module compile(CharSequence src) { + var tree = parser.parse(src); + return generateIR(tree); + } + boolean isReady() { return parser != null; } diff --git a/engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java b/engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java index 8eb6fe630fc..d3a6ef38b29 100644 --- a/engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java +++ b/engine/runtime/src/main/java/org/enso/compiler/TreeToIr.java @@ -42,7 +42,8 @@ import org.enso.compiler.core.IR$Module$Scope$Import; import org.enso.compiler.core.IR$Module$Scope$Import$Module; import org.enso.compiler.core.IR$Module$Scope$Import$Polyglot; import org.enso.compiler.core.IR$Module$Scope$Import$Polyglot$Java; -import org.enso.compiler.core.IR$Name$Annotation; +import org.enso.compiler.core.IR$Name$BuiltinAnnotation; +import org.enso.compiler.core.IR$Name$GenericAnnotation; import org.enso.compiler.core.IR$Name$Blank; import org.enso.compiler.core.IR$Name$Literal; import org.enso.compiler.core.IR$Name$Self; @@ -222,6 +223,7 @@ final class TreeToIr { private List translateModuleSymbolImpl(Tree inputAst, List appendTo) throws SyntaxException { return switch (inputAst) { case null -> appendTo; + case Tree.TypeDef def -> { var typeName = buildName(def.getName(), true); List irBody = nil(); @@ -238,6 +240,7 @@ final class TreeToIr { ); yield cons(type, appendTo); } + case Tree.Function fn -> { var methodRef = translateMethodReference(fn.getName(), false); var args = translateArgumentsDefinition(fn.getArgs()); @@ -256,6 +259,7 @@ final class TreeToIr { ); yield cons(binding, appendTo); } + case Tree.ForeignFunction fn when fn.getBody() instanceof Tree.TextLiteral body -> { var name = fn.getName(); var nameLoc = getIdentifiedLocation(name); @@ -277,13 +281,21 @@ final class TreeToIr { } case Tree.AnnotatedBuiltin anno -> { - var annotation = new IR$Name$Annotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); + var annotation = new IR$Name$BuiltinAnnotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); yield translateModuleSymbol(anno.getExpression(), cons(annotation, appendTo)); } + + case Tree.Annotated anno -> { + var annotationArgument = translateExpression(anno.getArgument()); + var annotation = new IR$Name$GenericAnnotation(anno.getAnnotation().codeRepr(), annotationArgument, getIdentifiedLocation(anno), meta(), diag()); + yield translateModuleSymbol(anno.getExpression(), cons(annotation, appendTo)); + } + case Tree.Documented doc -> { var comment = translateComment(doc, doc.getDocumentation()); yield translateModuleSymbol(doc.getExpression(), cons(comment, appendTo)); } + case Tree.Assignment a -> { var reference = translateMethodReference(a.getPattern(), false); var body = translateExpression(a.getExpr()); @@ -307,6 +319,7 @@ final class TreeToIr { var ascription = new IR$Type$Ascription(methodReference, signature, getIdentifiedLocation(sig), meta(), diag()); yield cons(ascription, appendTo); } + default -> { var error = translateSyntaxError(inputAst, IR$Error$Syntax$UnexpectedExpression$.MODULE$); yield cons(error, appendTo); @@ -328,7 +341,7 @@ final class TreeToIr { var constructorName = buildName(inputAst, cons.getConstructor()); List args = translateArgumentsDefinition(cons.getArguments()); var cAt = getIdentifiedLocation(inputAst); - return new IR$Module$Scope$Definition$Data(constructorName, args, cAt, meta(), diag()); + return new IR$Module$Scope$Definition$Data(constructorName, args, nil(), cAt, meta(), diag()); } catch (SyntaxException ex) { return ex.toError(); } @@ -353,12 +366,16 @@ final class TreeToIr { var inputAst = maybeManyParensed(exp); return switch (inputAst) { case null -> appendTo; + case Tree.ConstructorDefinition cons -> cons(translateConstructorDefinition(cons, inputAst), appendTo); + case Tree.TypeDef def -> { var ir = translateSyntaxError(def, IR$Error$Syntax$UnexpectedDeclarationInType$.MODULE$); yield cons(ir, appendTo); } + case Tree.ArgumentBlockApplication app -> appendTo; + case Tree.TypeSignature sig -> { var isMethod = false; if (sig.getVariable() instanceof Tree.Ident ident) { @@ -368,6 +385,7 @@ final class TreeToIr { var ir = translateTypeSignature(sig, sig.getType(), typeName); yield cons(ir, appendTo); } + case Tree.Function fun -> { IR.Name name; if (fun.getName() instanceof Tree.Ident ident) { @@ -379,6 +397,7 @@ final class TreeToIr { var ir = translateFunction(fun, name, fun.getArgs(), fun.getBody()); yield cons(ir, appendTo); } + // In some cases this is a `Function` in IR, but an `Assignment` in Tree. // See: https://discord.com/channels/401396655599124480/1001476608957349917 case Tree.Assignment assignment -> { @@ -387,6 +406,7 @@ final class TreeToIr { var ir = translateFunction(assignment, name, args, assignment.getExpr()); yield cons(ir, appendTo); } + case Tree.ForeignFunction fn when fn.getBody() instanceof Tree.TextLiteral body -> { var name = buildName(fn.getName()); var args = translateArgumentsDefinition(fn.getArgs()); @@ -406,11 +426,19 @@ final class TreeToIr { var irDoc = translateComment(doc, doc.getDocumentation()); yield translateTypeBodyExpression(doc.getExpression(), cons(irDoc, appendTo)); } + case Tree.AnnotatedBuiltin anno -> { - var ir = new IR$Name$Annotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); + var ir = new IR$Name$BuiltinAnnotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); var annotation = translateAnnotation(ir, anno.getExpression(), nil()); yield cons(annotation, appendTo); } + + case Tree.Annotated anno -> { + var annotationArgument = translateExpression(anno.getArgument()); + var annotation = new IR$Name$GenericAnnotation(anno.getAnnotation().codeRepr(), annotationArgument, getIdentifiedLocation(anno), meta(), diag()); + yield translateTypeBodyExpression(anno.getExpression(), cons(annotation, appendTo)); + } + default -> { var ir = translateSyntaxError(inputAst, IR$Error$Syntax$UnexpectedDeclarationInType$.MODULE$); yield cons(ir, appendTo); @@ -937,7 +965,7 @@ final class TreeToIr { case Tree.TemplateFunction templ -> translateExpression(templ.getAst(), false); case Tree.Wildcard wild -> new IR$Name$Blank(getIdentifiedLocation(wild), meta(), diag()); case Tree.AnnotatedBuiltin anno -> { - var ir = new IR$Name$Annotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); + var ir = new IR$Name$BuiltinAnnotation("@" + anno.getAnnotation().codeRepr(), getIdentifiedLocation(anno), meta(), diag()); yield translateAnnotation(ir, anno.getExpression(), nil()); } // Documentation can be attached to an expression in a few cases, like if someone documents a line of an @@ -1133,7 +1161,7 @@ final class TreeToIr { return new IR$Application$Prefix(pref.function(), withBlockArgs, pref.hasDefaultsSuspended(), pref.location(), meta(), diag()); } - private IR$Application$Prefix translateAnnotation(IR$Name$Annotation ir, Tree expr, List callArgs) { + private IR$Application$Prefix translateAnnotation(IR$Name$BuiltinAnnotation ir, Tree expr, List callArgs) { return switch (expr) { case Tree.App fn -> { var fnAsArg = translateCallArgument(fn.getArg()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java index 738cf565147..9dd9e9cec66 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java @@ -1,7 +1,6 @@ package org.enso.interpreter.node; import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.dsl.ReportPolymorphism; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.NodeInfo; @@ -26,7 +25,7 @@ public abstract class BaseNode extends Node { } } - private @CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL; + private @CompilerDirectives.CompilationFinal TailStatus tailStatus = TailStatus.NOT_TAIL; /** * Sets the new tail position status for this node. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java index 08ef30fbc5e..86332206692 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java @@ -2,6 +2,7 @@ package org.enso.interpreter.node.expression.builtin.meta; import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.Annotation; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.callable.atom.Atom; @@ -9,7 +10,6 @@ import org.enso.interpreter.runtime.callable.atom.StructsLibrary; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.data.Vector; import org.enso.interpreter.runtime.error.PanicException; import com.oracle.truffle.api.CompilerDirectives; @@ -19,12 +19,10 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnknownIdentifierException; -import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; -import com.oracle.truffle.api.profiles.ValueProfile; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.state.State; @@ -96,7 +94,7 @@ public abstract class AtomWithAHoleNode extends Node { }; } - @ExportMessage Object getMembers(boolean includeInternal) throws UnsupportedMessageException { + @ExportMessage Object getMembers(boolean includeInternal) { return new Array("value", "fill"); } @@ -118,7 +116,6 @@ public abstract class AtomWithAHoleNode extends Node { } static final class SwapAtomFieldNode extends RootNode { private final FunctionSchema schema; - private final ValueProfile sameAtom = ValueProfile.createClassProfile(); @CompilerDirectives.CompilationFinal private int lastIndex = -1; @Child private StructsLibrary structs = StructsLibrary.getFactory().createDispatched(10); @@ -130,7 +127,7 @@ public abstract class AtomWithAHoleNode extends Node { new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE) }, new boolean[]{ true, false - }, new CallArgumentInfo[0]); + }, new CallArgumentInfo[0], new Annotation[0]); } static SwapAtomFieldNode create() { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java new file mode 100644 index 00000000000..edea86d0de3 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/GetAnnotationNode.java @@ -0,0 +1,76 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.library.CachedLibrary; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.BaseNode; +import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode; +import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.Annotation; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; +import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.state.State; + +@BuiltinMethod( + type = "Meta", + name = "get_annotation", + description = "Get annotation associated with an object", + autoRegister = false) +public abstract class GetAnnotationNode extends BaseNode { + + abstract Object execute( + VirtualFrame frame, State state, Object target, Object method, Object parameter); + + @Specialization + Object doExecute( + VirtualFrame frame, + State state, + Object target, + Object method, + Object parameter, + @CachedLibrary(limit = "3") TypesLibrary types, + @Cached ThunkExecutorNode thunkExecutorNode, + @Cached ExpectStringNode expectStringNode) { + String methodName = expectStringNode.execute(method); + Type targetType = types.getType(target); + ModuleScope scope = targetType.getDefinitionScope(); + Function methodFunction = scope.lookupMethodDefinition(targetType, methodName); + if (methodFunction != null) { + String parameterName = expectStringNode.execute(parameter); + Annotation annotation = methodFunction.getSchema().getAnnotation(parameterName); + if (annotation != null) { + Function thunk = + Function.thunk(annotation.getExpression().getCallTarget(), frame.materialize()); + return thunkExecutorNode.executeThunk(thunk, state, getTailStatus()); + } + } + AtomConstructor constructor = getAtomConstructor(targetType, methodName); + if (constructor != null) { + Function constructorFunction = constructor.getConstructorFunction(); + String parameterName = expectStringNode.execute(parameter); + Annotation annotation = constructorFunction.getSchema().getAnnotation(parameterName); + if (annotation != null) { + Function thunk = + Function.thunk(annotation.getExpression().getCallTarget(), frame.materialize()); + return thunkExecutorNode.executeThunk(thunk, state, getTailStatus()); + } + } + return EnsoContext.get(this).getNothing(); + } + + static GetAnnotationNode build() { + return GetAnnotationNodeGen.create(); + } + + @CompilerDirectives.TruffleBoundary + private static AtomConstructor getAtomConstructor(Type type, String name) { + return type.getConstructors().get(name); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/Annotation.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/Annotation.java new file mode 100644 index 00000000000..2b94f5f710a --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/Annotation.java @@ -0,0 +1,25 @@ +package org.enso.interpreter.runtime.callable; + +import com.oracle.truffle.api.nodes.RootNode; + +/** Annotation with callable expression. */ +public class Annotation { + + private final RootNode expression; + private final String name; + + public Annotation(String name, RootNode expression) { + this.name = name; + this.expression = expression; + } + + /** @return the annotation name. */ + public String getName() { + return name; + } + + /** @return the annotation expression. */ + public RootNode getExpression() { + return expression; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java index 904055d1e22..8e32bb27e07 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/argument/CallArgumentInfo.java @@ -70,15 +70,15 @@ public final class CallArgumentInfo { * Represents a mapping between the defined arguments of a function and the call site arguments. */ public static class ArgumentMappingBuilder { - private int[] appliedMapping; - private int[] oversaturatedArgumentMapping; - private boolean[] argumentShouldExecute; - private ArgumentDefinition[] definitions; - private CallArgumentInfo[] callArgs; - private CallArgumentInfo[] existingOversaturatedArgs; - private boolean[] argumentUsed; - private boolean[] callSiteArgApplied; - private FunctionSchema originalSchema; + private final int[] appliedMapping; + private final int[] oversaturatedArgumentMapping; + private final boolean[] argumentShouldExecute; + private final ArgumentDefinition[] definitions; + private final CallArgumentInfo[] callArgs; + private final CallArgumentInfo[] existingOversaturatedArgs; + private final boolean[] argumentUsed; + private final boolean[] callSiteArgApplied; + private final FunctionSchema originalSchema; private int oversaturatedWritePosition = 0; /** @@ -220,7 +220,11 @@ public final class CallArgumentInfo { newOversaturatedArgInfo.length); return new FunctionSchema( - originalSchema.getCallerFrameAccess(), definitions, argumentUsed, oversaturatedArgInfo); + originalSchema.getCallerFrameAccess(), + definitions, + argumentUsed, + oversaturatedArgInfo, + originalSchema.getAnnotations()); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 0c2308dab1d..bfb1616b3fb 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -19,6 +19,7 @@ import org.enso.interpreter.node.expression.atom.InstantiateNode; import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; import org.enso.interpreter.runtime.callable.atom.unboxing.Layout; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.Annotation; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.FunctionSchema; @@ -50,7 +51,7 @@ public final class AtomConstructor implements TruffleObject { /** * Creates a new Atom constructor for a given name. The constructor is not valid until {@link - * AtomConstructor#initializeFields(LocalScope, ExpressionNode[], ExpressionNode[], + * AtomConstructor#initializeFields(LocalScope, ExpressionNode[], ExpressionNode[], Annotation[], * ArgumentDefinition...)} is called. * * @param name the name of the Atom constructor @@ -62,7 +63,7 @@ public final class AtomConstructor implements TruffleObject { /** * Creates a new Atom constructor for a given name. The constructor is not valid until {@link - * AtomConstructor#initializeFields(LocalScope, ExpressionNode[], ExpressionNode[], + * AtomConstructor#initializeFields(LocalScope, ExpressionNode[], ExpressionNode[], Annotation[], * ArgumentDefinition...)} is called. * * @param name the name of the Atom constructor @@ -95,7 +96,8 @@ public final class AtomConstructor implements TruffleObject { for (int i = 0; i < args.length; i++) { reads[i] = ReadArgumentNode.build(i, null); } - return initializeFields(LocalScope.root(), new ExpressionNode[0], reads, args); + return initializeFields( + LocalScope.root(), new ExpressionNode[0], reads, new Annotation[0], args); } /** @@ -111,6 +113,7 @@ public final class AtomConstructor implements TruffleObject { LocalScope localScope, ExpressionNode[] assignments, ExpressionNode[] varReads, + Annotation[] annotations, ArgumentDefinition... args) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -122,7 +125,8 @@ public final class AtomConstructor implements TruffleObject { if (Layout.isAritySupported(args.length)) { boxedLayout = Layout.create(args.length, 0); } - this.constructorFunction = buildConstructorFunction(localScope, assignments, varReads, args); + this.constructorFunction = + buildConstructorFunction(localScope, assignments, varReads, annotations, args); generateQualifiedAccessor(); return this; } @@ -144,6 +148,7 @@ public final class AtomConstructor implements TruffleObject { LocalScope localScope, ExpressionNode[] assignments, ExpressionNode[] varReads, + Annotation[] annotations, ArgumentDefinition[] args) { ExpressionNode instantiateNode = InstantiateNode.build(this, varReads); @@ -159,7 +164,7 @@ public final class AtomConstructor implements TruffleObject { null, false); RootCallTarget callTarget = rootNode.getCallTarget(); - return new Function(callTarget, null, new FunctionSchema(args)); + return new Function(callTarget, null, new FunctionSchema(annotations, args)); } private void generateQualifiedAccessor() { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java index 8856fa4740f..c13822de0c9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java @@ -16,6 +16,7 @@ import org.enso.interpreter.node.callable.InteropApplicationNode; import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode; import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.Annotation; import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.data.Array; @@ -102,7 +103,8 @@ public final class Function implements TruffleObject { public static Function fromBuiltinRootNodeWithCallerFrameAccess( BuiltinRootNode node, ArgumentDefinition... args) { RootCallTarget callTarget = node.getCallTarget(); - FunctionSchema schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, args); + FunctionSchema schema = + new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, new Annotation[0], args); return new Function(callTarget, null, schema); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java index 94362227ba3..67a89848175 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/FunctionSchema.java @@ -3,9 +3,12 @@ package org.enso.interpreter.runtime.callable.function; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.runtime.callable.Annotation; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import java.util.Arrays; + /** * Holds the definition site argument information together with information on the partially applied * arguments positions. @@ -34,32 +37,35 @@ public final class FunctionSchema { private final @CompilationFinal(dimensions = 1) ArgumentDefinition[] argumentInfos; private final @CompilationFinal(dimensions = 1) boolean[] hasPreApplied; private final @CompilationFinal(dimensions = 1) CallArgumentInfo[] oversaturatedArguments; + private final @CompilationFinal(dimensions = 1) Annotation[] annotations; private final boolean hasAnyPreApplied; private final boolean hasOversaturatedArguments; private final CallerFrameAccess callerFrameAccess; - private final boolean isFullyApplied; /** * Creates an {@link FunctionSchema} instance. * * @param callerFrameAccess the declaration of whether access to caller frame is required for this - * function - * @param argumentInfos Definition site arguments information + * function. + * @param argumentInfos Definition site arguments information. * @param hasPreApplied A flags collection such that {@code hasPreApplied[i]} is true iff a - * function has a partially applied argument at position {@code i} + * function has a partially applied argument at position {@code i}. * @param oversaturatedArguments information about any unused, oversaturated arguments passed to - * this function so far + * this function so far. + * @param annotations the list of annotations defined on this function. */ public FunctionSchema( CallerFrameAccess callerFrameAccess, ArgumentDefinition[] argumentInfos, boolean[] hasPreApplied, - CallArgumentInfo[] oversaturatedArguments) { + CallArgumentInfo[] oversaturatedArguments, + Annotation[] annotations) { this.argumentInfos = argumentInfos; this.oversaturatedArguments = oversaturatedArguments; this.hasPreApplied = hasPreApplied; this.callerFrameAccess = callerFrameAccess; + this.annotations = annotations; boolean hasAnyPreApplied = false; for (boolean b : hasPreApplied) { if (b) { @@ -77,15 +83,20 @@ public final class FunctionSchema { * Creates an {@link FunctionSchema} instance assuming the function has no partially applied * arguments. * - * @param callerFrameAccess the declaration of need to access the caller frame from the function - * @param argumentInfos Definition site arguments information + * @param callerFrameAccess the declaration of need to access the caller frame from the function. + * @param argumentInfos Definition site arguments information. + * @param annotations the list of annotations defined on this function. */ - public FunctionSchema(CallerFrameAccess callerFrameAccess, ArgumentDefinition... argumentInfos) { + public FunctionSchema( + CallerFrameAccess callerFrameAccess, + Annotation[] annotations, + ArgumentDefinition... argumentInfos) { this( callerFrameAccess, argumentInfos, new boolean[argumentInfos.length], - new CallArgumentInfo[0]); + new CallArgumentInfo[0], + annotations); } /** @@ -94,10 +105,23 @@ public final class FunctionSchema { * *

Caller frame access is assumed to be {@link CallerFrameAccess#NONE}. * + * @param annotations the list of annotations defined on this function. + * @param argumentInfos Definition site arguments information. + */ + public FunctionSchema(Annotation[] annotations, ArgumentDefinition... argumentInfos) { + this(CallerFrameAccess.NONE, annotations, argumentInfos); + } + + /** + * Creates an {@link FunctionSchema} instance assuming the function has no annotations or + * partially applied arguments. + * + *

Caller frame access is assumed to be {@link CallerFrameAccess#NONE}. + * * @param argumentInfos Definition site arguments information */ public FunctionSchema(ArgumentDefinition... argumentInfos) { - this(CallerFrameAccess.NONE, argumentInfos); + this(CallerFrameAccess.NONE, new Annotation[0], argumentInfos); } /** @@ -196,6 +220,25 @@ public final class FunctionSchema { return callerFrameAccess; } + /** @return annotations defined on this function. */ + public Annotation[] getAnnotations() { + return annotations; + } + + /** + * Finds annotation by name. + * + * @param name the annotation name. + * @return the matching annotation expression. + */ + @CompilerDirectives.TruffleBoundary + public Annotation getAnnotation(String name) { + return Arrays.stream(annotations) + .filter(annotation -> annotation.getName().equals(name)) + .findFirst() + .orElse(null); + } + /** * Checks whether the function is already fully applied. * diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala index 59e2a076803..ba6303b44b8 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala @@ -85,7 +85,8 @@ class Passes( AliasAnalysis, DataflowAnalysis, CachePreferenceAnalysis, - UnusedBindings + UnusedBindings, + GenericAnnotations ) ) @@ -96,7 +97,11 @@ class Passes( * these dependencies. */ val passOrdering: List[PassGroup] = passes.getOrElse( - List(moduleDiscoveryPasses, globalTypingPasses, functionBodyPasses) + List( + moduleDiscoveryPasses, + globalTypingPasses, + functionBodyPasses + ) ) /** The ordered representation of all passes run by the compiler. */ diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala index da52a7f6686..1b5b2370756 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala @@ -135,7 +135,10 @@ object AstToIr { def translateModuleSymbol(inputAst: AST): Module.Scope.Definition = { inputAst match { case AST.Ident.Annotation.any(annotation) => - IR.Name.Annotation(annotation.name, getIdentifiedLocation(annotation)) + IR.Name.BuiltinAnnotation( + annotation.name, + getIdentifiedLocation(annotation) + ) case AstView.Atom(consName, args) => val newArgs = args.map(translateArgumentDefinition(_)) @@ -306,16 +309,22 @@ object AstToIr { inputAst match { case AST.Ident.Cons.any(cons) => IR.Module.Scope.Definition - .Data(buildName(cons), List(), getIdentifiedLocation(inputAst)) + .Data( + buildName(cons), + List(), + List(), + getIdentifiedLocation(inputAst) + ) case AstView.SpacedList(AST.Ident.Cons.any(cons) :: args) => IR.Module.Scope.Definition .Data( buildName(cons), args.map(translateArgumentDefinition(_)), + List(), getIdentifiedLocation(inputAst) ) case AST.Ident.Annotation.any(ann) => - IR.Name.Annotation(ann.name, getIdentifiedLocation(ann)) + IR.Name.BuiltinAnnotation(ann.name, getIdentifiedLocation(ann)) case AstView.FunctionSugar( AST.Ident.Var("foreign"), header, @@ -1040,7 +1049,7 @@ object AstToIr { buildName(identifier) } case AST.Ident.Annotation(name) => - Name.Annotation(name, getIdentifiedLocation(identifier)) + Name.BuiltinAnnotation(name, getIdentifiedLocation(identifier)) case AST.Ident.Cons(name) => if (name == Constants.Names.SELF_TYPE_ARGUMENT) { Name.SelfType(getIdentifiedLocation(identifier)) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 52cd1aa781d..56ce822ea5c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -23,6 +23,7 @@ import org.enso.compiler.pass.analyse.{ import org.enso.compiler.pass.optimise.ApplicationSaturation import org.enso.compiler.pass.resolve.{ ExpressionAnnotations, + GenericAnnotations, GlobalNames, MethodDefinitions, Patterns, @@ -67,6 +68,7 @@ import org.enso.interpreter.runtime.callable.function.{ Function => RuntimeFunction } import org.enso.interpreter.runtime.callable.{ + Annotation => RuntimeAnnotation, UnresolvedConversion, UnresolvedSymbol } @@ -80,6 +82,7 @@ import org.enso.interpreter.runtime.scope.{ import org.enso.interpreter.{Constants, EnsoLanguage} import java.math.BigInteger + import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -87,8 +90,7 @@ import scala.jdk.OptionConverters._ import scala.jdk.CollectionConverters._ /** This is an implementation of a codegeneration pass that lowers the Enso - * [[IR]] into the truffle [[org.enso.compiler.core.Core.Node]] structures that - * are actually executed. + * [[IR]] into the truffle structures that are actually executed. * * It should be noted that, as is, there is no support for cross-module links, * with each lowering pass operating solely on a single module. @@ -258,11 +260,42 @@ class IrToTruffle( } val (assignments, reads) = argumentExpressions.unzip + // build annotations + val annotations = atomDefn.annotations.map { annotation => + val scopeElements = Seq( + tpDef.name.name, + atomDefn.name.name, + annotation.name + ) + val scopeName = + scopeElements.mkString(Constants.SCOPE_SEPARATOR) + val expressionProcessor = new ExpressionProcessor( + scopeName, + scopeInfo.graph, + scopeInfo.graph.rootScope, + dataflowInfo + ) + val expressionNode = + expressionProcessor.run(annotation.expression) + val closureName = s"" + val closureRootNode = ClosureRootNode.build( + language, + expressionProcessor.scope, + moduleScope, + expressionNode, + makeSection(moduleScope, annotation.location), + closureName, + true, + false + ) + new RuntimeAnnotation(annotation.name, closureRootNode) + } if (!atomCons.isInitialized) { atomCons.initializeFields( localScope, assignments.toArray, reads.toArray, + annotations.toArray, argDefs: _* ) } @@ -384,7 +417,7 @@ class IrToTruffle( .map(fOpt => // Register builtin iff it has not been automatically registered at an early stage // of builtins initialization. - fOpt.filter(m => !m.isAutoRegister()).map(m => m.getFunction) + fOpt.filter(m => !m.isAutoRegister).map(m => m.getFunction) ) case fn: IR.Function => val bodyBuilder = @@ -405,12 +438,63 @@ class IrToTruffle( ) val callTarget = rootNode.getCallTarget val arguments = bodyBuilder.args() + // build annotations + val annotations = + methodDef.getMetadata(GenericAnnotations).toVector.flatMap { + meta => + meta.annotations + .collect { case annotation: IR.Name.GenericAnnotation => + val scopeElements = Seq( + cons.getName, + methodDef.methodName.name, + annotation.name + ) + val scopeName = + scopeElements.mkString(Constants.SCOPE_SEPARATOR) + val scopeInfo = annotation + .unsafeGetMetadata( + AliasAnalysis, + s"Missing scope information for annotation " + + s"${annotation.name} of method " + + scopeElements.init.mkString(Constants.SCOPE_SEPARATOR) + ) + .unsafeAs[AliasAnalysis.Info.Scope.Root] + val dataflowInfo = annotation.unsafeGetMetadata( + DataflowAnalysis, + "Missing dataflow information for annotation " + + s"${annotation.name} of method " + + scopeElements.init.mkString(Constants.SCOPE_SEPARATOR) + ) + val expressionProcessor = new ExpressionProcessor( + scopeName, + scopeInfo.graph, + scopeInfo.graph.rootScope, + dataflowInfo + ) + val expressionNode = + expressionProcessor.run(annotation.expression) + val closureName = + s"" + val closureRootNode = ClosureRootNode.build( + language, + expressionProcessor.scope, + moduleScope, + expressionNode, + makeSection(moduleScope, annotation.location), + closureName, + true, + false + ) + new RuntimeAnnotation(annotation.name, closureRootNode) + } + } + Right( Some( new RuntimeFunction( callTarget, null, - new FunctionSchema(arguments: _*) + new FunctionSchema(annotations.toArray, arguments: _*) ) ) ) @@ -1655,7 +1739,7 @@ class IrToTruffle( * @param application the function application to generate code for * @return the truffle nodes corresponding to `application` */ - def processApplication( + private def processApplication( application: IR.Application, subjectToInstrumentation: Boolean ): RuntimeExpression = diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala index d92f9e0fc05..f83c2a5435c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala @@ -21,7 +21,6 @@ class FreshNameSupply { /** Generates a name guaranteed not to exist in this program. * - * @param isReferent whether or not the name should be marked as referent. * @param isMethod whether or not the name should represent a method name. * @return a new name */ diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala index ce510cd6923..c03d4cb084b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala @@ -75,6 +75,7 @@ final class SuggestionBuilder[A: IndexedSource]( arguments, _, _, + _, _ ) => buildAtomConstructor( diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index b4c07897f86..3cd97b93018 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -11,6 +11,7 @@ import org.enso.interpreter.epb.EpbParser import org.enso.syntax.text.{AST, Debug, Location} import java.util.UUID + import scala.annotation.unused /** [[IR]] is a temporary and fairly unsophisticated internal representation @@ -1068,6 +1069,7 @@ object IR { * * @param name the name of the atom * @param arguments the arguments to the atom constructor + * @param annotations the list of annotations * @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 @@ -1075,6 +1077,7 @@ object IR { sealed case class Data( name: IR.Name, arguments: List[DefinitionArgument], + annotations: List[IR.Name.GenericAnnotation], override val location: Option[IdentifiedLocation], override val passData: MetadataStorage = MetadataStorage(), override val diagnostics: DiagnosticStorage = DiagnosticStorage() @@ -1086,6 +1089,7 @@ object IR { * * @param name the name of the atom * @param arguments the arguments to the atom constructor + * @param annotations the list of annotations * @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 @@ -1093,14 +1097,22 @@ object IR { * @return a copy of `this`, updated with the specified values */ def copy( - name: IR.Name = name, - arguments: List[DefinitionArgument] = arguments, - location: Option[IdentifiedLocation] = location, - passData: MetadataStorage = passData, - diagnostics: DiagnosticStorage = diagnostics, - id: Identifier = id + name: IR.Name = name, + arguments: List[DefinitionArgument] = arguments, + annotations: List[IR.Name.GenericAnnotation] = annotations, + location: Option[IdentifiedLocation] = location, + passData: MetadataStorage = passData, + diagnostics: DiagnosticStorage = diagnostics, + id: Identifier = id ): Data = { - val res = Data(name, arguments, location, passData, diagnostics) + val res = Data( + name, + arguments, + annotations, + location, + passData, + diagnostics + ) res.id = id res } @@ -1142,17 +1154,19 @@ object IR { /** @inheritdoc */ override def mapExpressions(fn: Expression => Expression): Data = { copy( - name = name.mapExpressions(fn), - arguments = arguments.map(_.mapExpressions(fn)) + name = name.mapExpressions(fn), + arguments = arguments.map(_.mapExpressions(fn)), + annotations = annotations.map(_.mapExpressions(fn)) ) } /** @inheritdoc */ override def toString: String = s""" - |IR.Module.Scope.Definition.Atom( + |IR.Module.Scope.Definition.Data( |name = $name, |arguments = $arguments, + |annotations = $annotations, |location = $location, |passData = ${this.showPassData}, |diagnostics = $diagnostics, @@ -1161,7 +1175,7 @@ object IR { |""".toSingleLine /** @inheritdoc */ - override def children: List[IR] = name :: arguments + override def children: List[IR] = name :: arguments ::: annotations /** @inheritdoc */ override def showCode(indent: Int): String = { @@ -2754,20 +2768,37 @@ object IR { override def showCode(indent: Int): String = name } - /** The representation of an annotation name. + /** Base trait for annotations. */ + sealed trait Annotation extends Name with IR.Module.Scope.Definition { + + /** @inheritdoc */ + override def mapExpressions(fn: Expression => Expression): Annotation + + /** @inheritdoc */ + override def setLocation(location: Option[IdentifiedLocation]): Annotation + + /** @inheritdoc */ + override def duplicate( + keepLocations: Boolean = true, + keepMetadata: Boolean = true, + keepDiagnostics: Boolean = true, + keepIdentifiers: Boolean = false + ): Annotation + } + + /** The representation of builtin annotation. * * @param name the annotation text of the name * @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 */ - sealed case class Annotation( + sealed case class BuiltinAnnotation( override val name: String, override val location: Option[IdentifiedLocation], override val passData: MetadataStorage = MetadataStorage(), override val diagnostics: DiagnosticStorage = DiagnosticStorage() - ) extends Name - with IR.Module.Scope.Definition + ) extends Annotation with IRKind.Primitive { override protected var id: Identifier = randomId @@ -2786,8 +2817,8 @@ object IR { passData: MetadataStorage = passData, diagnostics: DiagnosticStorage = diagnostics, id: Identifier = id - ): Annotation = { - val res = Annotation(name, location, passData, diagnostics) + ): BuiltinAnnotation = { + val res = BuiltinAnnotation(name, location, passData, diagnostics) res.id = id res } @@ -2798,7 +2829,7 @@ object IR { keepMetadata: Boolean = true, keepDiagnostics: Boolean = true, keepIdentifiers: Boolean = false - ): Annotation = + ): BuiltinAnnotation = copy( location = if (keepLocations) location else None, passData = @@ -2811,17 +2842,19 @@ object IR { /** @inheritdoc */ override def setLocation( location: Option[IdentifiedLocation] - ): Annotation = + ): BuiltinAnnotation = copy(location = location) /** @inheritdoc */ - override def mapExpressions(fn: Expression => Expression): Annotation = + override def mapExpressions( + fn: Expression => Expression + ): BuiltinAnnotation = this /** @inheritdoc */ override def toString: String = s""" - |IR.Name.Annotation( + |IR.Name.BuiltinAnnotation( |name = $name, |location = $location, |passData = ${this.showPassData}, @@ -2834,7 +2867,97 @@ object IR { override def children: List[IR] = List() /** @inheritdoc */ - override def showCode(indent: Int): String = name + override def showCode(indent: Int): String = s"@$name" + } + + /** Common annotations of form `@name expression`. + * + * @param name the annotation text of the name + * @param expression the annotation expression + * @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 + */ + sealed case class GenericAnnotation( + override val name: String, + expression: Expression, + override val location: Option[IdentifiedLocation], + override val passData: MetadataStorage = MetadataStorage(), + override val diagnostics: DiagnosticStorage = DiagnosticStorage() + ) extends Annotation { + override protected var id: Identifier = randomId + + /** Creates a copy of `this`. + * + * @param name the annotation text of the name + * @param expression the annotation expression + * @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 + * @param id the identifier for the new node + * @return a copy of `this`, updated with the specified values + */ + def copy( + name: String = name, + expression: Expression = expression, + location: Option[IdentifiedLocation] = location, + passData: MetadataStorage = passData, + diagnostics: DiagnosticStorage = diagnostics, + id: Identifier = id + ): GenericAnnotation = { + val res = + GenericAnnotation(name, expression, location, passData, diagnostics) + res.id = id + res + } + + /** @inheritdoc */ + override def duplicate( + keepLocations: Boolean = true, + keepMetadata: Boolean = true, + keepDiagnostics: Boolean = true, + keepIdentifiers: Boolean = false + ): GenericAnnotation = + copy( + location = if (keepLocations) location else None, + passData = + if (keepMetadata) passData.duplicate else MetadataStorage(), + diagnostics = + if (keepDiagnostics) diagnostics.copy else DiagnosticStorage(), + id = if (keepIdentifiers) id else randomId + ) + + /** @inheritdoc */ + override def setLocation( + location: Option[IdentifiedLocation] + ): GenericAnnotation = + copy(location = location) + + /** @inheritdoc */ + override def mapExpressions( + fn: Expression => Expression + ): GenericAnnotation = + copy(expression = fn(expression)) + + /** @inheritdoc */ + override def toString: String = + s""" + |IR.Name.GenericAnnotation( + |name = $name, + |expression = $expression, + |location = $location, + |passData = ${this.showPassData}, + |diagnostics = $diagnostics, + |id = $id + |) + |""".toSingleLine + + /** @inheritdoc */ + override def children: List[IR] = List(expression) + + /** @inheritdoc */ + override def showCode(indent: Int): String = + s"@$name ${expression.showCode(indent)}" } /** A representation of the name `self`, used to refer to the current type. @@ -4420,7 +4543,7 @@ object IR { /** @inheritdoc */ override def toString: String = s""" - |IR.Function.Sugar( + |IR.Function.Binding( |name = $name, |arguments = $arguments, |body = $body, @@ -7512,7 +7635,7 @@ object IR { /** A representation of an Enso syntax error. * - * @param ast the erroneous AST + * @param at the erroneous AST * @param reason the cause of this error * @param passData the pass metadata associated with this node * @param diagnostics compiler diagnostics for this node @@ -8863,7 +8986,7 @@ object IR { @throws[CompilerError] def unsafeGetMetadata[K <: IRPass]( pass: IRPass, - msg: String + msg: => String ): pass.Metadata = { ir.passData.getUnsafe(pass)(msg) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/ir/MetadataStorage.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/ir/MetadataStorage.scala index fa3630456ca..8547710bfce 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/ir/MetadataStorage.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/ir/MetadataStorage.scala @@ -77,7 +77,7 @@ class MetadataStorage( @throws[CompilerError] def getUnsafe[K <: IRPass]( pass: K - )(msg: String = s"Missing metadata for pass $pass"): pass.Metadata = { + )(msg: => String = s"Missing metadata for pass $pass"): pass.Metadata = { get(pass).getOrElse(throw new CompilerError(msg)) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/PassManager.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/PassManager.scala index f55078a9dc8..a2a822b1ed1 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/PassManager.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/PassManager.scala @@ -28,7 +28,7 @@ class PassManager( * @throws CompilerError if a valid pass ordering cannot be computed * @return a valid pass ordering for the compiler, based on `passes` */ - def verifyPassOrdering(passes: List[IRPass]): List[IRPass] = { + private def verifyPassOrdering(passes: List[IRPass]): List[IRPass] = { var validPasses: Set[IRPass] = Set() passes.foreach(pass => { @@ -161,7 +161,7 @@ class PassManager( * @param passGroup the pass group being run * @return `true` if the condition holds, otherwise `false` */ - def isLastRunOf( + private def isLastRunOf( indexOfPassInGroup: Int, pass: IRPass, passGroup: PassGroup diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index d8f690be7f1..32239dee929 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -14,7 +14,6 @@ import org.enso.syntax.text.Debug import java.io.Serializable import scala.collection.mutable -import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag /** This pass performs scope identification and analysis, as well as symbol @@ -257,12 +256,23 @@ case object AliasAnalysis extends IRPass { ), members = t.members.map(d => { val graph = new Graph - d.copy(arguments = - analyseArgumentDefs( + d.copy( + arguments = analyseArgumentDefs( d.arguments, graph, graph.rootScope - ) + ), + annotations = d.annotations.map { ann => + ann + .copy( + expression = analyseExpression( + ann.expression, + topLevelGraph, + topLevelGraph.rootScope + ) + ) + .updateMetadata(this -->> Info.Scope.Root(topLevelGraph)) + } ).updateMetadata(this -->> Info.Scope.Root(graph)) }) ).updateMetadata(this -->> Info.Scope.Root(topLevelGraph)) @@ -280,11 +290,21 @@ case object AliasAnalysis extends IRPass { "Type signatures should not exist at the top level during " + "alias analysis." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of alias " + "analysis." ) + case ann: IR.Name.GenericAnnotation => + ann + .copy(expression = + analyseExpression( + ann.expression, + topLevelGraph, + topLevelGraph.rootScope + ) + ) + .updateMetadata(this -->> Info.Scope.Root(topLevelGraph)) case err: IR.Error => err } } @@ -561,7 +581,7 @@ case object AliasAnalysis extends IRPass { * @param parentScope the scope in which the arguments are defined * @return `args`, with aliasing information attached to each argument */ - def analyseCallArguments( + private def analyseCallArguments( args: List[IR.CallArgument], graph: AliasAnalysis.Graph, parentScope: AliasAnalysis.Graph.Scope @@ -1207,7 +1227,8 @@ case object AliasAnalysis extends IRPass { mapping.get(this) match { case Some(newCorrespondingScope) => newCorrespondingScope case None => - val childScopeCopies: mutable.ListBuffer[Scope] = ListBuffer() + val childScopeCopies: mutable.ListBuffer[Scope] = + mutable.ListBuffer() this.childScopes.foreach(scope => childScopeCopies += scope.deepCopy(mapping) ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala index 98f170dc102..242e8f83ea0 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/CachePreferenceAnalysis.scala @@ -117,12 +117,13 @@ case object CachePreferenceAnalysis extends IRPass { "Type signatures should not exist at the top level during " + "cache preference analysis." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "cache preference analysis." ) - case err: IR.Error => err + case ann: IR.Name.GenericAnnotation => ann + case err: IR.Error => err } /** Performs preference analysis on an arbitrary expression. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index 9b4e47b8b2a..3e6a170c44c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -149,22 +149,21 @@ case object DataflowAnalysis extends IRPass { info.dependencies.updateAt(tpDep, Set(paramDep)) analyseDefinitionArgument(param, info) } - val newMembers = members.map { - case data @ IR.Module.Scope.Definition.Data(_, arguments, _, _, _) => - val dataDep = asStatic(data) - info.dependents.updateAt(dataDep, Set(tpDep)) - info.dependencies.updateAt(tpDep, Set(dataDep)) - arguments.foreach(arg => { - val argDep = asStatic(arg) - info.dependents.updateAt(argDep, Set(dataDep)) - info.dependencies.updateAt(dataDep, Set(argDep)) - }) + val newMembers = members.map { data => + val dataDep = asStatic(data) + info.dependents.updateAt(dataDep, Set(tpDep)) + info.dependencies.updateAt(tpDep, Set(dataDep)) + data.arguments.foreach(arg => { + val argDep = asStatic(arg) + info.dependents.updateAt(argDep, Set(dataDep)) + info.dependencies.updateAt(dataDep, Set(argDep)) + }) - data - .copy( - arguments = arguments.map(analyseDefinitionArgument(_, info)) - ) - .updateMetadata(this -->> info) + data + .copy( + arguments = data.arguments.map(analyseDefinitionArgument(_, info)) + ) + .updateMetadata(this -->> info) } tp.copy(params = newParams, members = newMembers) .updateMetadata(this -->> info) @@ -187,11 +186,15 @@ case object DataflowAnalysis extends IRPass { "Type signatures should not exist at the top level during " + "dataflow analysis." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "dataflow analysis." ) + case ann: IR.Name.GenericAnnotation => + ann + .copy(expression = analyseExpression(ann.expression, info)) + .updateMetadata(this -->> info) case err: IR.Error => err } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala index 4f2b3627ce1..0d525bd1963 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/TailCall.scala @@ -88,7 +88,7 @@ case object TailCall extends IRPass { * @param definition the top-level definition to analyse * @return `definition`, annotated with tail call information */ - def analyseModuleBinding( + private def analyseModuleBinding( definition: IR.Module.Scope.Definition ): IR.Module.Scope.Definition = { definition match { @@ -126,11 +126,17 @@ case object TailCall extends IRPass { "Type signatures should not exist at the top level during " + "tail call analysis." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "tail call analysis." ) + case ann: IR.Name.GenericAnnotation => + ann + .copy(expression = + analyseExpression(ann.expression, isInTailPosition = true) + ) + .updateMetadata(this -->> TailPosition.Tail) case err: IR.Error => err } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala index 9406d0167d3..320f6803f1e 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala @@ -27,8 +27,6 @@ import org.enso.compiler.pass.resolve.{ } import org.enso.compiler.core.ir.MetadataStorage._ -import scala.annotation.unused - /** Desugars complex type definitions to simple type definitions in the module * scope. * @@ -75,7 +73,7 @@ case object ComplexType extends IRPass { */ override def runModule( ir: IR.Module, - @unused moduleContext: ModuleContext + moduleContext: ModuleContext ): IR.Module = ir.copy( bindings = ir.bindings.flatMap { @@ -94,12 +92,12 @@ case object ComplexType extends IRPass { */ override def runExpression( ir: IR.Expression, - @unused inlineContext: InlineContext + inlineContext: InlineContext ): IR.Expression = ir /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -110,12 +108,25 @@ case object ComplexType extends IRPass { * @param typ the type definition to desugar * @return the top-level definitions corresponding to the desugaring of `typ` */ - def desugarComplexType( + private def desugarComplexType( typ: IR.Module.Scope.Definition.SugaredType ): List[IR.Module.Scope.Definition] = { - val annotations = typ.getMetadata(ModuleAnnotations) + val annotations = typ.getMetadata(ModuleAnnotations) + var lastAnnotations = Seq.empty[IR.Name.GenericAnnotation] + var seenAnnotations = Set.empty[IR.Name.GenericAnnotation] val atomDefs = typ.body - .collect { case d: IR.Module.Scope.Definition.Data => d } + .flatMap { + case ann: IR.Name.GenericAnnotation => + lastAnnotations :+= ann + None + case d: IR.Module.Scope.Definition.Data => + val res = Some(d.copy(annotations = d.annotations ++ lastAnnotations)) + seenAnnotations ++= lastAnnotations + lastAnnotations = Seq() + res + case _ => + None + } // TODO[MK] this is probably removable .map(atom => annotations @@ -133,6 +144,7 @@ case object ComplexType extends IRPass { val remainingEntities = typ.body.filterNot { case _: IR.Module.Scope.Definition.Data => true + case ann: IR.Name.GenericAnnotation => seenAnnotations.contains(ann) case _ => false } @@ -185,7 +197,8 @@ case object ComplexType extends IRPass { matchSignaturesAndGenerate(name, binding) case funSugar @ IR.Function.Binding(name, _, _, _, _, _, _) => matchSignaturesAndGenerate(name, funSugar) - case err: IR.Error => Seq(err) + case err: IR.Error => Seq(err) + case ann: IR.Name.GenericAnnotation => Seq(ann) case _ => throw new CompilerError("Unexpected IR node in complex type body.") } @@ -217,11 +230,11 @@ case object ComplexType extends IRPass { * The signature _must_ correctly match the method definition. * * @param ir the definition to generate a method from - * @param names the names on which the method is being defined + * @param typeName the type name on which the method is being defined * @param signature the type signature for the method, if it exists * @return `ir` as a method */ - def genMethodDef( + private def genMethodDef( ir: IR, typeName: IR.Name, signature: Option[IR.Type.Ascription] @@ -280,7 +293,7 @@ case object ComplexType extends IRPass { * @param signature the method's type signature, if it exists * @return a top-level method definition */ - def genForName( + private def genForName( typeName: IR.Name, name: IR.Name, args: List[IR.DefinitionArgument], diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala index 0ad119bff35..270905bdb98 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/FunctionBinding.scala @@ -18,8 +18,6 @@ import org.enso.compiler.pass.optimise.LambdaConsolidate import org.enso.compiler.pass.resolve.IgnoredBindings import org.enso.interpreter.Constants -import scala.annotation.unused - /** This pass handles the desugaring of long-form function and method * definitions into standard bindings using lambdas. * @@ -64,7 +62,7 @@ case object FunctionBinding extends IRPass { */ override def runModule( ir: IR.Module, - @unused moduleContext: ModuleContext + moduleContext: ModuleContext ): IR.Module = ir.copy(bindings = ir.bindings.map(desugarModuleSymbol)) /** Runs desugaring of function bindings on an arbitrary expression. @@ -77,12 +75,12 @@ case object FunctionBinding extends IRPass { */ override def runExpression( ir: IR.Expression, - @unused inlineContext: InlineContext + inlineContext: InlineContext ): IR.Expression = desugarExpression(ir) /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -319,13 +317,14 @@ case object FunctionBinding extends IRPass { "Documentation should not be present during function binding" + "desugaring." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "function binding desugaring." ) - case a: IR.Type.Ascription => a - case e: IR.Error => e + case a: IR.Name.GenericAnnotation => a + case a: IR.Type.Ascription => a + case e: IR.Error => e } } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala index 7109fcf0941..5fcc7f1bb39 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/DocumentationComments.scala @@ -9,8 +9,6 @@ import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.desugar.{ComplexType, GenerateMethodBodies} -import scala.annotation.unused - /** Associates doc comments with the commented entities as metadata. * * If the first module definition is a documentation comment, it is treated as @@ -61,7 +59,7 @@ case object DocumentationComments extends IRPass { /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -94,6 +92,8 @@ case object DocumentationComments extends IRPass { private def resolveList[T <: IR](items: List[T]): List[T] = { var lastDoc: Option[IR.Comment.Documentation] = None items.flatMap { + case annotation: IR.Name.Annotation => + Some(annotation.asInstanceOf[T]) case doc: IR.Comment.Documentation => lastDoc = Some(doc) None @@ -158,10 +158,11 @@ case object DocumentationComments extends IRPass { method.copy(body = resolveExpression(method.body)) case tpe: IR.Module.Scope.Definition.SugaredType => tpe.copy(body = resolveList(tpe.body).map(resolveIr)) - case doc: IR.Comment.Documentation => doc - case tySig: IR.Type.Ascription => tySig - case err: IR.Error => err - case _: IR.Name.Annotation => + case doc: IR.Comment.Documentation => doc + case tySig: IR.Type.Ascription => tySig + case err: IR.Error => err + case ann: IR.Name.GenericAnnotation => ann + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "documentation comment resolution." diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ExpressionAnnotations.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ExpressionAnnotations.scala index ce12e6aa232..c25c5564681 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ExpressionAnnotations.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ExpressionAnnotations.scala @@ -8,8 +8,6 @@ import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.AliasAnalysis import org.enso.compiler.pass.resolve.ModuleAnnotations.Annotations -import scala.annotation.unused - case object ExpressionAnnotations extends IRPass { val tailCallName = "@Tail_Call" val builtinMethodName = "@Builtin_Method" @@ -62,7 +60,7 @@ case object ExpressionAnnotations extends IRPass { /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -71,7 +69,7 @@ case object ExpressionAnnotations extends IRPass { ): IR.Expression = ir.transformExpressions { case app @ IR.Application.Prefix( - ann: IR.Name.Annotation, + ann: IR.Name.BuiltinAnnotation, arguments, _, _, @@ -104,7 +102,7 @@ case object ExpressionAnnotations extends IRPass { IR.Error.Resolution(ann, IR.Error.Resolution.UnknownAnnotation) app.copy(function = err) } - case ann: IR.Name.Annotation => + case ann: IR.Name.BuiltinAnnotation => if (isKnownAnnotation(ann.name)) { IR.Error.Resolution( ann, @@ -120,7 +118,7 @@ case object ExpressionAnnotations extends IRPass { * @param name the annotation name to check * @return `true` if `name` is a known annotation, otherwise `false` */ - def isKnownAnnotation(name: String): Boolean = { + private def isKnownAnnotation(name: String): Boolean = { knownAnnotations.contains(name) } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GenericAnnotations.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GenericAnnotations.scala new file mode 100644 index 00000000000..f32f3f075a0 --- /dev/null +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GenericAnnotations.scala @@ -0,0 +1,89 @@ +package org.enso.compiler.pass.resolve + +import org.enso.compiler.context.{InlineContext, ModuleContext} +import org.enso.compiler.core.IR +import org.enso.compiler.core.IR.Module.Scope.Definition +import org.enso.compiler.core.ir.MetadataStorage._ +import org.enso.compiler.exception.CompilerError +import org.enso.compiler.pass.IRPass + +/** A pass responsible for the discovery of [[IR.Name.GenericAnnotation]] + * annotations, and for associating them with the corresponding construct. + * + * Compilation pipeline of generic annotations: + * - [[ModuleAnnotations]] pass ignores generic annotations and leaves them in + * the tree so that the consequent passes are able to process the annotation + * expression. + * - [[org.enso.compiler.pass.desugar.ComplexType]] pass associates generic + * annotations with the type constructor definitions. + * - [[GenericAnnotations]] pass associates generic annotations that are left + * in the tree with the appropriate definitions. + */ +case object GenericAnnotations extends IRPass { + override type Metadata = ModuleAnnotations.Annotations + override type Config = IRPass.Configuration.Default + override val precursorPasses: Seq[IRPass] = Seq() + override val invalidatedPasses: Seq[IRPass] = Seq() + + /** Resolves annotations. + * + * @param ir the Enso IR to process + * @param moduleContext a context object that contains the information needed + * to process a module + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runModule( + ir: IR.Module, + moduleContext: ModuleContext + ): IR.Module = { + var lastAnnotations: Seq[IR.Name.GenericAnnotation] = Seq() + val newBindings = ir.bindings.map { + case _: IR.Name.BuiltinAnnotation => + throw new CompilerError( + s"Builtin annotations should not be present at generic annotations pass." + ) + case _: Definition.SugaredType => + throw new CompilerError( + s"Sugared types should not be present at generic annotations pass." + ) + case _: IR.Comment => + throw new CompilerError( + "Comments should not be present at generic annotations pass." + ) + case ann: IR.Name.GenericAnnotation => + lastAnnotations :+= ann + None + case entity => + val res = Some( + entity.updateMetadata( + this -->> ModuleAnnotations.Annotations(lastAnnotations) + ) + ) + lastAnnotations = Seq() + res + } + ir.copy(bindings = newBindings.flatten) + } + + /** Execute the pass on an expression. + * + * As the pass only deals with module-level annotations this is a no-op. + * + * @param ir the Enso IR to process + * @param inlineContext a context object that contains the information needed + * for inline evaluation + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runExpression( + ir: IR.Expression, + inlineContext: InlineContext + ): IR.Expression = ir + + /** @inheritdoc */ + override def updateMetadataInDuplicate[T <: IR]( + sourceIr: T, + copyOfIr: T + ): T = copyOfIr +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala index 8d1f68addfd..fdea377ed47 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala @@ -15,8 +15,6 @@ import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis} import org.enso.interpreter.Constants -import scala.annotation.unused - /** Resolves name occurences in non-pattern contexts. * * 1. Attaches resolution metadata to encountered constructors, modules, @@ -61,7 +59,7 @@ case object GlobalNames extends IRPass { ) val freshNameSupply = moduleContext.freshNameSupply.getOrElse( throw new CompilerError( - "No fresh name supply passed to UppercaseNames resolver." + "No fresh name supply passed to GlobalNames resolver." ) ) val new_bindings = @@ -96,7 +94,7 @@ case object GlobalNames extends IRPass { /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -139,7 +137,7 @@ case object GlobalNames extends IRPass { freshNameSupply: FreshNameSupply, selfTypeResolution: Option[Resolution], isInsideApplication: Boolean = false - ): IR.Expression = + ): IR.Expression = { ir.transformExpressions { case selfTp: IR.Name.SelfType => selfTypeResolution @@ -239,6 +237,7 @@ case object GlobalNames extends IRPass { } } + } private def resolveReferantApplication( app: IR.Application.Prefix, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleAnnotations.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleAnnotations.scala index e7b3a75aa39..0063d5a7e0c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleAnnotations.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/ModuleAnnotations.scala @@ -13,8 +13,6 @@ import org.enso.compiler.pass.desugar.{ GenerateMethodBodies } -import scala.annotation.unused - /** A pass responsible for the discovery of module annotations, and for * associating them with the corresponding construct. */ @@ -34,7 +32,7 @@ case object ModuleAnnotations extends IRPass { * @param ir the Enso IR to process * @param moduleContext a context object that contains the information needed * to process a module - * @return `ir`, possibly having made transformations or annotations to that + * @return `ir`, possibly having made transformations or annotations to that * IR. */ override def runModule( @@ -42,27 +40,27 @@ case object ModuleAnnotations extends IRPass { moduleContext: ModuleContext ): IR.Module = { var lastAnnotations: Seq[IR.Name.Annotation] = Seq() - val newBindings = for (binding <- ir.bindings) yield { - binding match { - case ann: Name.Annotation => - lastAnnotations :+= ann - None - case comment: IR.Comment => Some(comment) - case typ: Definition.SugaredType => - val res = Some( - resolveComplexType(typ).updateMetadata( - this -->> Annotations(lastAnnotations) - ) + val newBindings = ir.bindings.map { + case ann: Name.BuiltinAnnotation => + lastAnnotations :+= ann + None + case ann: Name.GenericAnnotation => + Some(ann) + case comment: IR.Comment => Some(comment) + case typ: Definition.SugaredType => + val res = Some( + resolveComplexType(typ).updateMetadata( + this -->> Annotations(lastAnnotations) ) - lastAnnotations = Seq() - res - case entity => - val res = Some( - entity.updateMetadata(this -->> Annotations(lastAnnotations)) - ) - lastAnnotations = Seq() - res - } + ) + lastAnnotations = Seq() + res + case entity => + val res = Some( + entity.updateMetadata(this -->> Annotations(lastAnnotations)) + ) + lastAnnotations = Seq() + res } ir.copy(bindings = newBindings.flatten) } @@ -72,14 +70,16 @@ case object ModuleAnnotations extends IRPass { * @param typ the type in which to resolve annotations * @return `typ` with all top-level annotations resolved */ - def resolveComplexType( + private def resolveComplexType( typ: Definition.SugaredType ): Definition.SugaredType = { var lastAnnotations: Seq[IR.Name.Annotation] = Seq() val newBodyElems = typ.body.flatMap { - case ann: Name.Annotation => + case ann: Name.BuiltinAnnotation => lastAnnotations :+= ann None + case ann: Name.GenericAnnotation => + Some(ann) case comment: IR.Comment => Some(comment) case entity => val res = Some( @@ -98,17 +98,17 @@ case object ModuleAnnotations extends IRPass { * @param ir the Enso IR to process * @param inlineContext a context object that contains the information needed * for inline evaluation - * @return `ir`, possibly having made transformations or annotations to that + * @return `ir`, possibly having made transformations or annotations to that * IR. */ override def runExpression( ir: IR.Expression, - @unused inlineContext: InlineContext + inlineContext: InlineContext ): IR.Expression = ir /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala index beffe7f262f..b714453f86b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala @@ -2,10 +2,10 @@ package org.enso.compiler.pass.resolve import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.IR +import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.desugar.{ComplexType, GenerateMethodBodies} -import scala.annotation.unused import scala.collection.mutable /** This pass performs static detection of method overloads and emits errors @@ -45,59 +45,56 @@ case object OverloadsResolution extends IRPass { */ override def runModule( ir: IR.Module, - @unused moduleContext: ModuleContext + moduleContext: ModuleContext ): IR.Module = { var seenTypes: Set[String] = Set() var seenMethods: Map[Option[String], Set[(String, Boolean)]] = Map() val types = ir.bindings.collect { - case tp: IR.Module.Scope.Definition.Type => tp - } - - val newTypes: List[IR.Module.Scope.Definition] = types.map(tp => { - if (seenTypes.contains(tp.name.name)) { - IR.Error.Redefined.Type(tp.name, tp.location) - } else { - seenTypes = seenTypes + tp.name.name + case tp: IR.Module.Scope.Definition.Type => tp - } - }) - - val methods = ir.bindings.collect { + } + ir.bindings.collect { case meth: IR.Module.Scope.Definition.Method.Explicit => - seenMethods = seenMethods + (meth.typeName.map(_.name) -> Set()) + seenMethods += meth.typeName.map(_.name) -> Set() meth } - - val newMethods: List[IR.Module.Scope.Definition] = methods.map(method => { - if ( - seenMethods(method.typeName.map(_.name)) - .contains((method.methodName.name, method.isStatic)) - ) { - IR.Error.Redefined - .Method(method.typeName, method.methodName, method.location) - } else { - types.find(_.name.name.equals(method.methodName.name)) match { - case Some(clashedAtom) if method.typeName.isEmpty => - IR.Error.Redefined.MethodClashWithAtom( - clashedAtom.name, - method.methodName, - method.location - ) - case _ => - val currentMethods: Set[(String, Boolean)] = - seenMethods(method.typeName.map(_.name)) - seenMethods = seenMethods + (method.typeName.map(_.name) -> - (currentMethods + ((method.methodName.name, method.isStatic)))) - method - } - } - }) - val conversionsForType: mutable.Map[Option[String], Set[String]] = mutable.Map() - val conversions: List[IR.Module.Scope.Definition] = ir.bindings.collect { + val newBindings = ir.bindings.map { + case tp: IR.Module.Scope.Definition.Type => + if (seenTypes.contains(tp.name.name)) { + IR.Error.Redefined.Type(tp.name, tp.location) + } else { + seenTypes += tp.name.name + tp + } + + case method: IR.Module.Scope.Definition.Method.Explicit => + if ( + seenMethods(method.typeName.map(_.name)) + .contains((method.methodName.name, method.isStatic)) + ) { + IR.Error.Redefined + .Method(method.typeName, method.methodName, method.location) + } else { + types.find(_.name.name.equals(method.methodName.name)) match { + case Some(clashedAtom) if method.typeName.isEmpty => + IR.Error.Redefined.MethodClashWithAtom( + clashedAtom.name, + method.methodName, + method.location + ) + case _ => + val currentMethods: Set[(String, Boolean)] = + seenMethods(method.typeName.map(_.name)) + seenMethods += (method.typeName.map(_.name) -> + (currentMethods + ((method.methodName.name, method.isStatic)))) + method + } + } + case m: IR.Module.Scope.Definition.Method.Conversion => val fromName = m.sourceTypeName.asInstanceOf[IR.Name] conversionsForType.get(m.typeName.map(_.name)) match { @@ -115,15 +112,32 @@ case object OverloadsResolution extends IRPass { conversionsForType.put(m.typeName.map(_.name), Set(fromName.name)) m } + + case diagnostic: IR.Diagnostic => diagnostic + case ann: IR.Name.GenericAnnotation => ann + case _: IR.Type.Ascription => + throw new CompilerError( + "Type ascriptions should not be present during the overloads resolution." + ) + case _: IR.Module.Scope.Definition.Method.Binding => + throw new CompilerError( + "Method bindings should not be present during the overloads resolution." + ) + case _: IR.Name.BuiltinAnnotation => + throw new CompilerError( + "Builtin annotations should not be present during the overloads resolution." + ) + case _: IR.Comment.Documentation => + throw new CompilerError( + "Documentation comments should not be present during the overloads resolution." + ) + case _: IR.Module.Scope.Definition.SugaredType => + throw new CompilerError( + "Sugared types should not be present during the overloads resolution." + ) } - val diagnostics = ir.bindings.collect { case diag: IR.Diagnostic => - diag - } - - ir.copy( - bindings = newTypes ::: newMethods ::: conversions ::: diagnostics - ) + ir.copy(bindings = newBindings) } /** This pass does nothing for the expression flow. @@ -141,7 +155,7 @@ case object OverloadsResolution extends IRPass { /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala index f3bde1f8723..58f4cb20bd5 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala @@ -13,8 +13,6 @@ import org.enso.compiler.pass.lint.UnusedBindings import org.enso.compiler.pass.optimise.LambdaConsolidate import org.enso.compiler.pass.resolve.TypeSignatures.Signature -import scala.annotation.unused - /** This pass is responsible for analysing type signatures to determine which * arguments in a function definition are suspended. * @@ -69,7 +67,7 @@ case object SuspendedArguments extends IRPass { */ override def runModule( ir: IR.Module, - @unused moduleContext: ModuleContext + moduleContext: ModuleContext ): IR.Module = ir.copy( bindings = ir.bindings.map(resolveModuleBinding) @@ -85,12 +83,12 @@ case object SuspendedArguments extends IRPass { */ override def runExpression( ir: IR.Expression, - @unused inlineContext: InlineContext + inlineContext: InlineContext ): IR.Expression = resolveExpression(ir) /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -104,7 +102,7 @@ case object SuspendedArguments extends IRPass { * @param binding the top-level binding to resolve suspensions in * @return `binding`, with any suspended arguments resolved */ - def resolveModuleBinding( + private def resolveModuleBinding( binding: IR.Module.Scope.Definition ): IR.Module.Scope.Definition = { binding match { @@ -193,11 +191,12 @@ case object SuspendedArguments extends IRPass { throw new CompilerError("Type ascriptions should not be present.") case _: IR.Comment => throw new CompilerError("Comments should not be present.") - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "suspended arguments analysis." ) + case ann: IR.Name.GenericAnnotation => ann } } @@ -206,7 +205,7 @@ case object SuspendedArguments extends IRPass { * @param expression the expression to perform resolution in * @return `expression`, with any suspended arguments resolved */ - def resolveExpression(expression: IR.Expression): IR.Expression = { + private def resolveExpression(expression: IR.Expression): IR.Expression = { expression.transformExpressions { case bind @ IR.Expression.Binding(_, expr, _, _, _) => val newExpr = bind.getMetadata(TypeSignatures) match { @@ -244,7 +243,7 @@ case object SuspendedArguments extends IRPass { * @param signature the type signature to split * @return the segments of `signature` */ - def toSegments(signature: IR.Expression): List[IR.Expression] = { + private def toSegments(signature: IR.Expression): List[IR.Expression] = { signature match { case IR.Type.Function(args, ret, _, _, _) => args :+ ret case _ => List(signature) @@ -270,7 +269,7 @@ case object SuspendedArguments extends IRPass { * @param pair an argument and its corresponding type signature segment * @return the argument from `pair`, with its suspension marked appropriately */ - def markSuspended( + private def markSuspended( pair: (IR.DefinitionArgument, IR.Expression) ): IR.DefinitionArgument = pair match { @@ -289,7 +288,7 @@ case object SuspendedArguments extends IRPass { * @param signature the signature of the function * @return `args`, appropriately marked as suspended or not */ - def computeSuspensions( + private def computeSuspensions( args: List[IR.DefinitionArgument], signature: IR.Expression ): List[IR.DefinitionArgument] = { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala index a7b7e718565..d75118a0674 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/TypeSignatures.scala @@ -9,8 +9,6 @@ import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse._ import org.enso.compiler.pass.lint.UnusedBindings -import scala.annotation.unused - /** This pass is responsible for resolving type signatures and associating * them as metadata with the typed object. * @@ -49,7 +47,7 @@ case object TypeSignatures extends IRPass { */ override def runModule( ir: IR.Module, - @unused moduleContext: ModuleContext + moduleContext: ModuleContext ): IR.Module = resolveModule(ir) /** Resolves type signatures in an expression. @@ -62,12 +60,12 @@ case object TypeSignatures extends IRPass { */ override def runExpression( ir: IR.Expression, - @unused inlineContext: InlineContext + inlineContext: InlineContext ): IR.Expression = resolveExpression(ir) /** @inheritdoc */ override def updateMetadataInDuplicate[T <: IR]( - @unused sourceIr: T, + sourceIr: T, copyOfIr: T ): T = copyOfIr @@ -78,7 +76,7 @@ case object TypeSignatures extends IRPass { * @param mod the module to resolve signatures in * @return `mod`, with type signatures resolved */ - def resolveModule(mod: IR.Module): IR.Module = { + private def resolveModule(mod: IR.Module): IR.Module = { var lastSignature: Option[IR.Type.Ascription] = None val newBindings: List[IR.Module.Scope.Definition] = mod.bindings.flatMap { @@ -133,13 +131,14 @@ case object TypeSignatures extends IRPass { res case ut: IR.Module.Scope.Definition.Type => Some(ut.mapExpressions(resolveExpression)) - case err: IR.Error => Some(err) + case err: IR.Error => Some(err) + case ann: IR.Name.GenericAnnotation => Some(ann) case _: IR.Module.Scope.Definition.SugaredType => throw new CompilerError( "Complex type definitions should not be present during type " + "signature resolution." ) - case _: IR.Name.Annotation => + case _: IR.Name.BuiltinAnnotation => throw new CompilerError( "Annotations should already be associated by the point of " + "type signature resolution." @@ -163,7 +162,7 @@ case object TypeSignatures extends IRPass { * @param expr the expression to resolve signatures in * @return `expr`, with any type signatures resolved */ - def resolveExpression(expr: IR.Expression): IR.Expression = { + private def resolveExpression(expr: IR.Expression): IR.Expression = { expr.transformExpressions { case block: IR.Expression.Block => resolveBlock(block) case sig: IR.Type.Ascription => resolveAscription(sig) @@ -175,7 +174,7 @@ case object TypeSignatures extends IRPass { * @param sig the signature to convert * @return the typed expression in `sig`, with `signature` attached */ - def resolveAscription(sig: IR.Type.Ascription): IR.Expression = { + private def resolveAscription(sig: IR.Type.Ascription): IR.Expression = { val newTyped = sig.typed.mapExpressions(resolveExpression) val newSig = sig.signature.mapExpressions(resolveExpression) newTyped.updateMetadata(this -->> Signature(newSig)) @@ -186,7 +185,7 @@ case object TypeSignatures extends IRPass { * @param block the block to resolve signatures in * @return `block`, with any type signatures resolved */ - def resolveBlock(block: IR.Expression.Block): IR.Expression.Block = { + private def resolveBlock(block: IR.Expression.Block): IR.Expression.Block = { var lastSignature: Option[IR.Type.Ascription] = None val allBlockExpressions = block.expressions :+ block.returnValue diff --git a/engine/runtime/src/test/java/org/enso/compiler/AnnotationsCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/AnnotationsCompilerTest.java new file mode 100644 index 00000000000..d5e526fa0b9 --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/compiler/AnnotationsCompilerTest.java @@ -0,0 +1,97 @@ +package org.enso.compiler; + +import org.enso.compiler.core.IR$Function$Binding; +import org.enso.compiler.core.IR$Module$Scope$Definition$Data; +import org.enso.compiler.core.IR$Module$Scope$Definition$SugaredType; +import org.enso.compiler.core.IR$Name$Annotation; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AnnotationsCompilerTest extends CompilerTest { + + @Test + public void testModuleMethod() throws Exception { + var ir = parse(""" + @a expr + @b (x y) + foo a b = a b + """); + + var annotation1 = (IR$Name$Annotation) ir.bindings().apply(0); + var annotation2 = (IR$Name$Annotation) ir.bindings().apply(1); + + assertEquals(annotation1.name(), "a"); + assertEquals(annotation2.name(), "b"); + } + + @Test + public void testExtensionMethod() throws Exception { + var ir = parse(""" + @a expr + @b (x y) + Foo.foo a b = a b + """); + + var annotation1 = (IR$Name$Annotation) ir.bindings().apply(0); + var annotation2 = (IR$Name$Annotation) ir.bindings().apply(1); + + assertEquals(annotation1.name(), "a"); + assertEquals(annotation2.name(), "b"); + } + + @Test + public void testTypeMethod() throws Exception { + var ir = parse(""" + type Foo + @a foo + @b bar + method a b = a b + """); + + var typeDefinition = (IR$Module$Scope$Definition$SugaredType) ir.bindings().apply(0); + + var annotation1 = (IR$Name$Annotation) typeDefinition.body().apply(0); + var annotation2 = (IR$Name$Annotation) typeDefinition.body().apply(1); + var function = (IR$Function$Binding) typeDefinition.body().apply(2); + + assertEquals(annotation1.name(), "a"); + assertEquals(annotation2.name(), "b"); + assertEquals(function.name().name(), "method"); + } + + @Test + public void testConstructor() throws Exception { + var ir = parse(""" + type Foo + @a foo + @b bar + Cons a b = a b + """); + + var typeDefinition = (IR$Module$Scope$Definition$SugaredType) ir.bindings().apply(0); + + var annotation1 = (IR$Name$Annotation) typeDefinition.body().apply(0); + var annotation2 = (IR$Name$Annotation) typeDefinition.body().apply(1); + var constructor = (IR$Module$Scope$Definition$Data) typeDefinition.body().apply(2); + + assertEquals(annotation1.name(), "a"); + assertEquals(annotation2.name(), "b"); + assertEquals(constructor.name().name(), "Cons"); + } + + @Test + public void testInvalidComplexType() throws Exception { + var ir = parse(""" + type Foo + bar a = + """); + + var typeDefinition = (IR$Module$Scope$Definition$SugaredType) ir.bindings().apply(0); + var method = (IR$Function$Binding) typeDefinition.body().apply(0); + + assertEquals(method.name().name(), "bar"); + // FIXME method body is null. Should be `IR.Error.Syntax.UnexpectedDeclarationInType` + assertEquals(method.body(), null); + } +} diff --git a/engine/runtime/src/test/java/org/enso/compiler/CompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/CompilerTest.java new file mode 100644 index 00000000000..b0143a920f4 --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/compiler/CompilerTest.java @@ -0,0 +1,34 @@ +package org.enso.compiler; + +import com.oracle.truffle.api.source.Source; +import org.enso.compiler.core.IR; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; + +import static org.junit.Assert.assertNotNull; + +public abstract class CompilerTest { + + protected static EnsoCompiler ensoCompiler; + + @BeforeClass + public static void initEnsoCompiler() { + ensoCompiler = new EnsoCompiler(); + } + + @AfterClass + public static void closeEnsoCompiler() throws Exception { + ensoCompiler.close(); + } + + protected static IR.Module parse(String code) throws IOException { + var src = + Source.newBuilder("enso", code, "test-" + Integer.toHexString(code.hashCode()) + ".enso") + .build(); + IR.Module ir = ensoCompiler.compile(src); + assertNotNull("IR was generated", ir); + return ir; + } +} diff --git a/engine/runtime/src/test/java/org/enso/compiler/EnsoCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/EnsoCompilerTest.java index df9f4972a76..e6f1b47d25f 100644 --- a/engine/runtime/src/test/java/org/enso/compiler/EnsoCompilerTest.java +++ b/engine/runtime/src/test/java/org/enso/compiler/EnsoCompilerTest.java @@ -1229,7 +1229,6 @@ public class EnsoCompilerTest { equivalenceTest("a = x", "a = SKIP FREEZE x + y"); equivalenceTest("a = x", "a = SKIP FREEZE x.f"); equivalenceTest("a = x", "a = SKIP FREEZE x.f y"); - } @Test diff --git a/engine/runtime/src/test/java/org/enso/compiler/ErrorCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/ErrorCompilerTest.java index cbd74ae58a9..c27fdf5eaf3 100644 --- a/engine/runtime/src/test/java/org/enso/compiler/ErrorCompilerTest.java +++ b/engine/runtime/src/test/java/org/enso/compiler/ErrorCompilerTest.java @@ -1,9 +1,5 @@ package org.enso.compiler; -import com.oracle.truffle.api.source.Source; - -import java.io.IOException; - import org.enso.compiler.core.IR; import org.enso.compiler.core.IR$Error$Syntax; import org.enso.compiler.core.IR$Error$Syntax$InvalidEscapeSequence$; @@ -14,42 +10,28 @@ import org.enso.compiler.core.IR$Error$Syntax$UnrecognizedToken$; import org.enso.compiler.core.IR$Error$Syntax$UnsupportedSyntax; import org.enso.syntax.text.Location; -import org.junit.AfterClass; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import org.junit.BeforeClass; import org.junit.Test; import scala.collection.immutable.List; -public class ErrorCompilerTest { - private static EnsoCompiler ensoCompiler; - - @BeforeClass - public static void initEnsoCompiler() { - ensoCompiler = new EnsoCompiler(); - } - - @AfterClass - public static void closeEnsoCompiler() throws Exception { - ensoCompiler.close(); - } +public class ErrorCompilerTest extends CompilerTest { @Test public void spaceRequired() throws Exception { - var ir = parseTest("foo = if cond.x else.y"); + var ir = parse("foo = if cond.x else.y"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 8); } @Test public void incompleteTypeDefinition() throws Exception { - var ir = parseTest("type"); + var ir = parse("type"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 4); } @Test public void badCase1() throws Exception { - var ir = parseTest(""" + var ir = parse(""" foo = case x of 4 """); @@ -58,7 +40,7 @@ public class ErrorCompilerTest { @Test public void badCase2() throws Exception { - var ir = parseTest(""" + var ir = parse(""" foo = case x of 4 -> """); @@ -67,7 +49,7 @@ public class ErrorCompilerTest { @Test public void badCase3() throws Exception { - var ir = parseTest(""" + var ir = parse(""" foo = case x of 4-> """); @@ -76,7 +58,7 @@ public class ErrorCompilerTest { @Test public void badCase4() throws Exception { - var ir = parseTest(""" + var ir = parse(""" main = case value of -1 ->"minus one" @@ -86,253 +68,253 @@ public class ErrorCompilerTest { @Test public void malformedSequence1() throws Exception { - var ir = parseTest("(1, )"); + var ir = parse("(1, )"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 5); } @Test public void malformedSequence2() throws Exception { - var ir = parseTest("foo = (1, )"); + var ir = parse("foo = (1, )"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 7, 9); } @Test public void unmatchedDemiliter1() throws Exception { - var ir = parseTest("("); + var ir = parse("("); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 1); } @Test public void unmatchedDemiliter2() throws Exception { - var ir = parseTest(")"); + var ir = parse(")"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 1); } @Test public void unmatchedDemiliter3() throws Exception { - var ir = parseTest("["); + var ir = parse("["); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 1); } @Test public void unmatchedDemiliter4() throws Exception { - var ir = parseTest("["); + var ir = parse("["); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 1); } @Test public void unmatchedDemiliter5() throws Exception { - var ir = parseTest("foo = ("); + var ir = parse("foo = ("); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 7); } @Test public void unmatchedDemiliter6() throws Exception { - var ir = parseTest("foo = )"); + var ir = parse("foo = )"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 7); } @Test public void unmatchedDemiliter7() throws Exception { - var ir = parseTest("foo = ["); + var ir = parse("foo = ["); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 7); } @Test public void unmatchedDemiliter8() throws Exception { - var ir = parseTest("foo = ]"); + var ir = parse("foo = ]"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 7); } @Test public void unexpectedSpecialOperator() throws Exception { - var ir = parseTest("foo = 1, 2"); + var ir = parse("foo = 1, 2"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 6, 10); } @Test public void malformedImport1() throws Exception { - var ir = parseTest("import"); + var ir = parse("import"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 6); } @Test public void malformedImport2() throws Exception { - var ir = parseTest("import as Foo"); + var ir = parse("import as Foo"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 13); } @Test public void malformedImport3() throws Exception { - var ir = parseTest("import Foo as Foo, Bar"); + var ir = parse("import Foo as Foo, Bar"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 22); } @Test public void malformedImport4() throws Exception { - var ir = parseTest("import Foo as Foo.Bar"); + var ir = parse("import Foo as Foo.Bar"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 21); } @Test public void malformedImport5() throws Exception { - var ir = parseTest("import Foo as"); + var ir = parse("import Foo as"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 13, 13); } @Test public void malformedImport6() throws Exception { - var ir = parseTest("import Foo as Bar.Baz"); + var ir = parse("import Foo as Bar.Baz"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 21); } @Test public void malformedImport7() throws Exception { - var ir = parseTest("import Foo hiding"); + var ir = parse("import Foo hiding"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 17, 17); } @Test public void malformedImport8() throws Exception { - var ir = parseTest("import Foo hiding X,"); + var ir = parse("import Foo hiding X,"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 18, 20); } @Test public void malformedImport9() throws Exception { - var ir = parseTest("polyglot import Foo"); + var ir = parse("polyglot import Foo"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnrecognizedToken$.MODULE$, "Unrecognized token.", 0, 19); } @Test public void malformedImport10() throws Exception { - var ir = parseTest("polyglot java import"); + var ir = parse("polyglot java import"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 20); } @Test public void malformedImport11() throws Exception { - var ir = parseTest("from import all"); + var ir = parse("from import all"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 4, 4); } @Test public void malformedImport12() throws Exception { - var ir = parseTest("from Foo import all hiding"); + var ir = parse("from Foo import all hiding"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 26, 26); } @Test public void malformedImport13() throws Exception { - var ir = parseTest("from Foo import all hiding X.Y"); + var ir = parse("from Foo import all hiding X.Y"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 27, 30); } @Test public void malformedExport1() throws Exception { - var ir = parseTest("export"); + var ir = parse("export"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 6); } @Test public void malformedExport2() throws Exception { - var ir = parseTest("export as Foo"); + var ir = parse("export as Foo"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 13); } @Test public void malformedExport3() throws Exception { - var ir = parseTest("export Foo as Foo, Bar"); + var ir = parse("export Foo as Foo, Bar"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 22); } @Test public void malformedExport4() throws Exception { - var ir = parseTest("export Foo as Foo.Bar"); + var ir = parse("export Foo as Foo.Bar"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 21); } @Test public void malformedExport5() throws Exception { - var ir = parseTest("export Foo as"); + var ir = parse("export Foo as"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 13, 13); } @Test public void malformedExport6() throws Exception { - var ir = parseTest("export Foo as Bar.Baz"); + var ir = parse("export Foo as Bar.Baz"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 14, 21); } @Test public void malformedExport7() throws Exception { - var ir = parseTest("export Foo hiding"); + var ir = parse("export Foo hiding"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 7, 17); } @Test public void malformedExport8() throws Exception { - var ir = parseTest("export Foo hiding X,"); + var ir = parse("export Foo hiding X,"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 7, 20); } @Test public void malformedExport9() throws Exception { - var ir = parseTest("from export all"); + var ir = parse("from export all"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 4, 4); } @Test public void malformedExport10() throws Exception { - var ir = parseTest("from Foo export all hiding"); + var ir = parse("from Foo export all hiding"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 26, 26); } @Test public void malformedExport11() throws Exception { - var ir = parseTest("from Foo export all hiding X.Y"); + var ir = parse("from Foo export all hiding X.Y"); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidImport$.MODULE$, "Imports must have a valid module path.", 27, 30); } @Test public void invalidToken1() throws Exception { - var ir = parseTest("`"); + var ir = parse("`"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 1); } @Test public void invalidToken2() throws Exception { - var ir = parseTest("splice_outside_text = `"); + var ir = parse("splice_outside_text = `"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 22, 23); } @Test public void illegalForeignBody1() throws Exception { - var ir = parseTest("foreign 4"); + var ir = parse("foreign 4"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 9); } @Test public void illegalForeignBody2() throws Exception { - var ir = parseTest("foreign 4 * 4"); + var ir = parse("foreign 4 * 4"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 13); } @Test public void illegalForeignBody3() throws Exception { - var ir = parseTest("foreign foo = \"4\""); + var ir = parse("foreign foo = \"4\""); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 17); } @Test public void illegalForeignBody4() throws Exception { - var ir = parseTest("foreign js foo = 4"); + var ir = parse("foreign js foo = 4"); assertSingleSyntaxError(ir, IR$Error$Syntax$UnexpectedExpression$.MODULE$, "Unexpected expression.", 0, 18); } @Test public void illegalEscapeSequence() throws Exception { - var ir = parseTest(""" + var ir = parse(""" escape = 'wrong \\c sequence' """); assertSingleSyntaxError(ir, IR$Error$Syntax$InvalidEscapeSequence$.MODULE$.apply("wrong sequence"), "Invalid escape sequence wrong sequence.", 9, 28); @@ -340,7 +322,7 @@ public class ErrorCompilerTest { @Test public void testNPE183814303() throws Exception { - var ir = parseTest(""" + var ir = parse(""" from Standard.Base import all main = @@ -353,7 +335,7 @@ public class ErrorCompilerTest { @Test public void testNPE183863754() throws Exception { - var ir = parseTest(""" + var ir = parse(""" main = # meh 42 @@ -380,13 +362,4 @@ public class ErrorCompilerTest { assertEquals("Expecting errors: " + errors, count, errors.size()); return errors; } - - private static IR.Module parseTest(String code) throws IOException { - var src = - Source.newBuilder("enso", code, "test-" + Integer.toHexString(code.hashCode()) + ".enso") - .build(); - var ir = ensoCompiler.compile(src); - assertNotNull("IR was generated", ir); - return ir; - } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala index 074b34dfce5..c92a5ed7dbf 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala @@ -1,5 +1,6 @@ package org.enso.compiler.test +import org.enso.compiler.EnsoCompiler import org.enso.compiler.codegen.AstToIr import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext} import org.enso.compiler.core.IR @@ -18,6 +19,8 @@ import org.enso.pkg.QualifiedName trait CompilerTest extends AnyWordSpecLike with Matchers with CompilerRunner trait CompilerRunner { + def useRustParser: Boolean = false + // === IR Utilities ========================================================= /** Adds an extension method for converting a string to its AST @@ -61,7 +64,13 @@ trait CompilerRunner { * @return the [[IR]] representing [[source]] */ def toIrModule: IR.Module = { - AstToIr.translate(source.toAst) + if (useRustParser) { + val compiler = new EnsoCompiler() + try compiler.compile(source) + finally compiler.close() + } else { + AstToIr.translate(source.toAst) + } } } @@ -208,6 +217,7 @@ trait CompilerRunner { None ) ), + List(), None ) } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala index e1aae4bec3e..9e98e017129 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala @@ -1041,7 +1041,7 @@ class AstToIrTest extends CompilerTest with Inside { |type Foo a b |""".stripMargin.toIrModule - ir.bindings.head shouldBe an[IR.Name.Annotation] + ir.bindings.head shouldBe an[IR.Name.BuiltinAnnotation] ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.SugaredType] } @@ -1059,8 +1059,8 @@ class AstToIrTest extends CompilerTest with Inside { val complexType = ir.bindings.head.asInstanceOf[IR.Module.Scope.Definition.SugaredType] - complexType.body.head shouldBe an[IR.Name.Annotation] - complexType.body(2) shouldBe an[IR.Name.Annotation] + complexType.body.head shouldBe an[IR.Name.BuiltinAnnotation] + complexType.body(2) shouldBe an[IR.Name.BuiltinAnnotation] } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleAnnotationsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleAnnotationsTest.scala index cf30d4aaf0e..c4d63e1e048 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleAnnotationsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/ModuleAnnotationsTest.scala @@ -12,6 +12,8 @@ class ModuleAnnotationsTest extends CompilerTest { // === Test Setup =========================================================== + override val useRustParser = true + val passes = new Passes(defaultConfig) val precursorPasses: PassGroup = passes.getPrecursors(ModuleAnnotations).get @@ -115,60 +117,62 @@ class ModuleAnnotationsTest extends CompilerTest { anns.length shouldEqual 1 anns.head.asInstanceOf[IR.Name].name shouldEqual "@My_Annotation" } + + "not associate generic annotations with method definitions" in { + val ir = + """@a expr_1 + |@b expr_2 + |My_Type.add a b = this.frob(a + b) + |""".stripMargin.preprocessModule.resolve + + ir.bindings.length shouldEqual 3 + ir.bindings(0) shouldBe a[IR.Name.GenericAnnotation] + ir.bindings(1) shouldBe a[IR.Name.GenericAnnotation] + } + } "Annotation desugaring in complex types" should { implicit val moduleContext: ModuleContext = mkModuleContext - "associate annotations with atom definitions" in { + "not associate generic annotations with atom definitions" in { val ir = """@My_Annotation |type Foo - | @My_Annotation + | @my annotation | Bar |""".stripMargin.preprocessModule.resolve ir.bindings.length shouldEqual 1 ir.bindings.head shouldBe a[Definition.SugaredType] val typ = ir.bindings.head.asInstanceOf[Definition.SugaredType] - typ.body.length shouldEqual 1 - typ.body.head shouldBe a[Definition.Data] - typ.body.head - .unsafeGetMetadata(ModuleAnnotations, "") - .annotations - .head - .asInstanceOf[IR.Name] - .name shouldEqual "@My_Annotation" + typ.body.length shouldEqual 2 + typ.body(0) shouldBe an[IR.Name.GenericAnnotation] + typ.body(1) shouldBe a[Definition.Data] } - "associate annotations with method definitions" in { + "not associate generic annotations with method definitions" in { val ir = """type Foo | Foo | - | @My_Annotation + | @a expr | my_method a = a |""".stripMargin.preprocessModule.resolve ir.bindings.length shouldEqual 1 ir.bindings.head shouldBe a[Definition.SugaredType] val typ = ir.bindings.head.asInstanceOf[Definition.SugaredType] - typ.body.length shouldEqual 2 - typ.body(1) shouldBe an[IR.Function.Binding] - typ - .body(1) - .unsafeGetMetadata(ModuleAnnotations, "") - .annotations - .head - .asInstanceOf[IR.Name] - .name shouldEqual "@My_Annotation" + typ.body.length shouldEqual 3 + typ.body(1) shouldBe an[IR.Name.GenericAnnotation] + typ.body(2) shouldBe an[IR.Function.Binding] } "not associate annotations with comments" in { val ir = """ |type Foo - | @My_Annotation + | @my annotation | ## Doc comment | Foo |""".stripMargin.preprocessModule.resolve @@ -176,16 +180,10 @@ class ModuleAnnotationsTest extends CompilerTest { ir.bindings.length shouldEqual 1 ir.bindings.head shouldBe a[Definition.SugaredType] val typ = ir.bindings.head.asInstanceOf[Definition.SugaredType] - typ.body.length shouldEqual 2 - typ.body.head shouldBe an[IR.Comment] - typ.body(1) shouldBe a[Definition.Data] - typ - .body(1) - .unsafeGetMetadata(ModuleAnnotations, "") - .annotations - .head - .asInstanceOf[IR.Name] - .name shouldEqual "@My_Annotation" + typ.body.length shouldEqual 3 + typ.body(0) shouldBe an[IR.Name.GenericAnnotation] + typ.body(1) shouldBe an[IR.Comment] + typ.body(2) shouldBe a[Definition.Data] } } } diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index eb512bab7f7..98e955d9353 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -14,10 +14,27 @@ import Standard.Test.Extensions from Standard.Base.Error.Common import Uninitialized_State type My_Type + @foo (test_method) + @baz (My_Type.Value 1 2 3) Value foo bar baz + @param (test_method 5 6) + first_method self param = param + + second_method self param = param + + @a (test_method 5) + @b (self -> self.foo) + other_method self a = a + +@self ("se" + "lf") My_Type.my_method self = self.foo + self.bar + self.baz +@a (test_method 3 4) +@b (Test_Type.Value 49) +@c (Error.throw "Error Value") +test_method a b = a + b + type Test_Type Value x @@ -172,6 +189,26 @@ spec = (Test_Type.Value a)==(Test_Type.Value b) . should_be_true (Test_Type.Value a)==(Test_Type.Value c) . should_be_false + Test.specify "get annotations" <| + Meta.get_annotation Meta_Spec "test_method" "a" . should_equal 7 + Meta.get_annotation Meta_Spec "test_method" "b" . should_equal (Test_Type.Value 49) + Meta.get_annotation Meta_Spec "test_method" "c" . should_fail_with "Error Value" + Meta.get_annotation Meta_Spec "test_method" "x" . should_equal Nothing + + value = My_Type.Value 99 "bar" True + Meta.get_annotation value "first_method" "param" . should_equal 11 + Meta.get_annotation value "second_method" "param" . should_equal Nothing + Meta.get_annotation value "third_method" "param" . should_equal Nothing + Meta.get_annotation value "other_method" "a" 7 . should_equal 12 + Meta.get_annotation value "other_method" "b" value . should_equal 99 + Meta.get_annotation value "other_method" "c" . should_equal Nothing + + Meta.get_annotation value "my_method" "self" . should_equal "self" + + Meta.get_annotation value "Value" "foo" 7 8 . should_equal 15 + Meta.get_annotation value "Value" "bar" . should_equal Nothing + Meta.get_annotation value "Value" "baz" . should_equal (My_Type.Value 1 2 3) + Test.group "Atom with holes" <| Test.specify "construct and fill" <| pair = Meta.atom_with_hole (e -> My_Type.Value 1 e 3)