Document the runtime's builtin functionality (#1397)

This commit is contained in:
Ara Adkins 2021-01-14 15:31:15 +00:00 committed by GitHub
parent d30a80eedc
commit a7bd90ab8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 2785 additions and 207 deletions

View File

@ -370,7 +370,7 @@ val directoryWatcherVersion = "0.9.10"
val flatbuffersVersion = "1.12.0"
val guavaVersion = "29.0-jre"
val jlineVersion = "3.15.0"
val kindProjectorVersion = "0.11.0"
val kindProjectorVersion = "0.11.2"
val mockitoScalaVersion = "1.14.8"
val newtypeVersion = "0.4.4"
val pprintVersion = "0.5.9"

View File

@ -2,9 +2,9 @@ import Builtins
from Base import all
## Returns the root directory of the project.
Builtins.Enso_Project.root : File.File
Builtins.Enso_Project.root = File.File this.prim_root_file
Builtins.Project_Description.root : File.File
Builtins.Project_Description.root = File.File this.prim_root_file
## Returns the root data directory of the project.
Builtins.Enso_Project.data : File.File
Builtins.Enso_Project.data = this.root / "data"
Builtins.Project_Description.data : File.File
Builtins.Project_Description.data = this.root / "data"

View File

@ -29,3 +29,5 @@ dependencies, and Enso projects for use by our users.
working.
- [**Local Repository:**](local-repository.md) Explanation of local repository
structure that is used for bundling engine with project manager distributions.
- [**Standard Libraries:**](standard-libraries.md) A brief explanation of the
standard libraries for Enso.

View File

@ -0,0 +1,151 @@
---
layout: developer-doc
title: Standard Libraries
category: distribution
tags: [distribution, stdlib]
order: 9
---
# Standard Libraries
At the current stage, Enso ships with a small set of libraries that compose the
language's "standard library". This document provides a brief explanation of
these libraries, as well as notes on how they should be used.
<!-- MarkdownTOC levels="2,3" autolink="true" indent=" " -->
- [Base](#base)
- [Builtins](#builtins)
- [Table](#table)
- [Test](#test)
- [Documentation](#documentation)
- [General Libraries Notes](#general-libraries-notes)
<!-- /MarkdownTOC -->
## Base
`Base` is the core library of Enso. It contains core types and data structures,
as well as basic functionality for interacting with the outside world. It can be
found in
[`distribution/std-lib/Base`](https://github.com/enso-org/enso/tree/main/distribution/std-lib/Base).
`Base` is intended to be imported unqualified at the top of the file:
`from Base import all`. Items not included in this unqualified import are
considered to be more specialist or internal, and should be intentionally
imported by users.
### Builtins
In addition to the functionalities exposed in the standard library source, the
interpreter also contains a set of definitions that are considered "primitive"
and are hence built into the interpreter.
For the purposes of documentation, there is a
[`Builtins.enso`](https://github.com/enso-org/enso/tree/main/engine/runtime/src/main/resources/Builtins.enso)
file that provides stub definitions for these builtin functions. It is used for
documentation purposes, and must be kept up to date as the builtins change.
> #### Note: Shadow Definitions
>
> In time this file will be replaced by true shadow definitions for the language
> builtins. It is only a stop-gap measure to allow documenting this
> functionality at this point in time.
## Table
`Table` is Enso's dataframes library, providing functionality for loading and
analysing tabular data. It is a core data-science toolkit, that integrates
deeply with Enso and its IDE. It can be found in
[`distribution/std-lib/Table`](https://github.com/enso-org/enso/tree/main/distribution/std-lib/Table).
`Table` is designed to be imported unqualified: `from Table import all`. Items
not included in this unqualified import are considered to be more specialist or
internal, and should be intentionally imported by users.
## Test
`Test` is a library for testing and benchmarking Enso code. At this point in
time it is _very_ rudimentary, and needs significant improvement before we can
consider it an "official" part of the Enso standard libraries. It can be found
in
[`distribution/std-lib/Test`](https://github.com/enso-org/enso/tree/main/distribution/std-lib/Test).
`Test` is intended to be imported qualified: `import Test`. This ensures that
there aren't spurious name clashes between user-defined functionality and the
testing library.
## Documentation
These libraries are comprehensively documented, with all functionality
accompanied by comprehensive documentation comments. These are located _above_
each definition, for example:
```
## Sort the Vector.
Arguments:
- `on`: A projection from the element type to the value of that element
being sorted on.
- `by`: A function that compares the result of applying `on` to two
elements, returning an Ordering to compare them.
- `order`: The order in which the vector elements are sorted.
By default, elements are sorted in ascending order, using the comparator
`compare_to`. A custom comparator may be passed to the sort function.
This is a stable sort, meaning that items that compare the same will not
have their order changed by the sorting process.
The complexities for this sort are:
- *Worst-Case Time:* `O(n * log n)`
- *Best-Case Time:* `O(n)`
- *Average Time:* `O(n * log n)`
- *Worst-Case Space:* `O(n)` additional
? Implementation Note
The sort implementation is based upon an adaptive, iterative mergesort
that requires far fewer than `n * log(n)` comparisons when the vector
is partially sorted. When the vector is randomly ordered, the
performance is equivalent to a standard mergesort.
It takes equal advantage of ascending and descending runs in the array,
making it much simpler to merge two or more sorted arrays: simply
concatenate them and sort.
> Example
Sorting a vector of numbers.
[5, 2, 3, 45, 15].sort == [2, 3, 5, 15, 45]
> Example
Sorting a vector of `Pair`s on the first element, descending.
[Pair 1 2, Pair -1 8].sort (_.first) (order = Sort_Order.Descending)
sort : (Any -> Any) -> (Any -> Any -> Ordering) -> Sort_Order -> Vector
sort (on = x -> x) (by = (_.compare_to _)) (order = Sort_Order.Ascending) = ...
```
Such documentation blocks describe:
- **Summary:** A basic summary of the behaviour of the method.
- **Arguments:** Descriptions of each of the arguments to the function.
- **Additional Information:** Additional exposition about the method.
- **Note (Optional):** Optional notes containing potentially important
information for more experienced users.
- **Examples:** Examples of the method's usage, with descriptions.
In addition, a function will have a type signature that describes the expected
types of the function arguments. It may also have defaults for its arguments,
which will be shown in the
## General Libraries Notes
Some notes on the general structure of these libraries.
- All of these libraries are considered to be WIP as they are missing many
pieces of functionality that they should have.
- As the language doesn't currently have built-in support for access modifiers
(e.g. `private`), so we instead use `PRIVATE` annotations at the top of
documentation blocks. Any functionality annotated in such a form is not for
public consumption.
- The `Base.Meta` functionality is considered to be unstable as it is inherently
tied to the internals of the compiler and the interpreter.

View File

@ -111,6 +111,9 @@ state. The documentation syntax supports the following tags:
of the library.
- `REMOVED`: Used to describe constructs that have been removed and are no
longer functional.
- `PRIVATE`: Used to describe constructs that are private in the language.
- `ADVANCED`: Items that are _not_ private, but are for power users.
- `TEXT_ONLY`: Items that do not apply to the graphical mode.
Tags are added at the _top_ of the documentation block, and may also be
accompanied by a description. This description directly follows the tag

View File

@ -48,6 +48,8 @@ public final class Language extends TruffleLanguage<Context> {
/**
* Creates a new Enso context.
*
* <p>This method is meant to be fast, and hence should not perform any long-running logic.
*
* @param env the language execution environment
* @return a new Enso context
*/
@ -62,6 +64,16 @@ public final class Language extends TruffleLanguage<Context> {
return context;
}
/**
* Initialize the context.
*
* @param context the language context
*/
@Override
protected void initializeContext(Context context) throws Exception {
context.initialize();
}
/**
* Checks if this Enso execution environment is accessible in a multithreaded context.
*

View File

@ -11,20 +11,19 @@ import org.enso.interpreter.dsl.Suspend;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.error.RuntimeError;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Panic",
name = "catch",
name = "recover",
description = "Executes an action and converts any Panic thrown by it into an Error")
public abstract class CatchPanicNode extends Node {
public abstract class RecoverPanicNode extends Node {
private @Child ThunkExecutorNode thunkExecutorNode = ThunkExecutorNode.build();
static CatchPanicNode build() {
return CatchPanicNodeGen.create();
static RecoverPanicNode build() {
return RecoverPanicNodeGen.create();
}
abstract Stateful execute(@MonadicState Object state, Object _this, @Suspend Object action);

View File

@ -24,19 +24,19 @@ public abstract class CopyNode extends Node {
}
abstract Object execute(
Object _this, Object src, long source_index, Array that, long dest_index, long count);
Object _this, Object src, long source_index, Array dest, long dest_index, long count);
@Specialization
Object doArray(
Object _this,
Array src,
long source_index,
Array that,
Array dest,
long dest_index,
long count,
@CachedContext(Language.class) Context ctx) {
System.arraycopy(
src.getItems(), (int) source_index, that.getItems(), (int) dest_index, (int) count);
src.getItems(), (int) source_index, dest.getItems(), (int) dest_index, (int) count);
return ctx.getBuiltins().nothing().newInstance();
}
@ -45,14 +45,14 @@ public abstract class CopyNode extends Node {
Object _this,
Object src,
long source_index,
Array that,
Array dest,
long dest_index,
long count,
@CachedLibrary(limit = "3") InteropLibrary arrays,
@CachedContext(Language.class) Context ctx) {
try {
for (int i = 0; i < count; i++) {
that.getItems()[(int) dest_index + i] = arrays.readArrayElement(src, source_index + i);
dest.getItems()[(int) dest_index + i] = arrays.readArrayElement(src, source_index + i);
}
} catch (UnsupportedMessageException e) {
throw new IllegalStateException("Unreachable");
@ -65,7 +65,7 @@ public abstract class CopyNode extends Node {
@Fallback
Object doOther(
Object _this, Object src, long source_index, Array that, long dest_index, long count) {
Object _this, Object src, long source_index, Array dest, long dest_index, long count) {
Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
throw new PanicException(
builtins.error().makeTypeError(builtins.mutable().array().newInstance(), src), this);

View File

@ -5,7 +5,7 @@ import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.Ref;
@BuiltinMethod(type = "Ref", name = "new", description = "Creates an empty ref.")
@BuiltinMethod(type = "Ref", name = "new", description = "Creates a new ref.")
public class NewRefNode extends Node {
Object execute(Object _this, Object value) {

View File

@ -1,14 +1,33 @@
package org.enso.interpreter.node.expression.builtin.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.atom.Atom;
@BuiltinMethod(type = "Runtime", name = "gc", description = "Forces garbage collection")
public class GCNode extends Node {
public abstract class GCNode extends Node {
public abstract Object execute(Object _this);
/** @return A new GCNode. */
public static GCNode build() {
return GCNodeGen.create();
}
@Specialization
Object doGc(Object _this, @CachedContext(Language.class) Context context) {
runGC();
return context.getBuiltins().nothing().newInstance();
}
@CompilerDirectives.TruffleBoundary
Object execute(Object _this) {
private void runGC() {
System.gc();
return 0;
}
}

View File

@ -7,7 +7,7 @@ import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.data.text.Text;
@BuiltinMethod(
type = "Prim_Text_Helpers",
type = "Prim_Text_Helper",
name = "optimize",
description = "Forces flattening of a text value, for testing or purposes.")
public class OptimizeNode extends Node {

View File

@ -24,7 +24,7 @@ public class EnsoProjectNode extends RootNode {
if (pkgOpt.isPresent()) {
Package<TruffleFile> pkg = pkgOpt.get();
Object rootPath = context.getEnvironment().asGuestValue(new EnsoFile(pkg.root().normalize()));
result = context.getBuiltins().getEnsoProject().newInstance(rootPath);
result = context.getBuiltins().getProjectDescription().newInstance(rootPath);
} else {
result =
new RuntimeError(context.getBuiltins().error().moduleNotInPackageError().newInstance());

View File

@ -1,8 +1,19 @@
package org.enso.interpreter.runtime;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.enso.compiler.Compiler;
import org.enso.home.HomeManager;
import org.enso.interpreter.Language;
@ -17,13 +28,6 @@ import org.enso.pkg.PackageManager;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.RuntimeOptions;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* The language context is the internal state of the language that is associated with each thread in
* a running Enso program.
@ -37,11 +41,13 @@ public class Context {
private final PrintStream err;
private final InputStream in;
private final BufferedReader inReader;
private final List<Package<TruffleFile>> packages;
private final TopLevelScope topScope;
private List<Package<TruffleFile>> packages;
private @CompilationFinal TopLevelScope topScope;
private final ThreadManager threadManager;
private final ResourceManager resourceManager;
private final boolean isCachingDisabled;
private final Builtins builtins;
private final String home;
/**
* Creates a new Enso context.
@ -59,8 +65,18 @@ public class Context {
this.threadManager = new ThreadManager();
this.resourceManager = new ResourceManager(this);
this.isCachingDisabled = environment.getOptions().get(RuntimeOptions.DISABLE_INLINE_CACHES_KEY);
TruffleFileSystem fs = new TruffleFileSystem();
this.home = home;
builtins = new Builtins(this);
this.compiler = new Compiler(this, builtins);
}
/** Perform expensive initialization logic for the context. */
public void initialize() {
this.getCompiler().initializeBuiltinsIr();
TruffleFileSystem fs = new TruffleFileSystem();
packages = new ArrayList<>();
if (home != null) {
@ -94,9 +110,8 @@ public class Context {
Collectors.toMap(
srcFile -> srcFile.qualifiedName().toString(),
srcFile -> new Module(srcFile.qualifiedName(), srcFile.file())));
topScope = new TopLevelScope(new Builtins(this), knownFiles);
this.compiler = new Compiler(this);
topScope = new TopLevelScope(builtins, knownFiles);
}
public TruffleFile getTruffleFile(File file) {

View File

@ -1,10 +1,17 @@
package org.enso.interpreter.runtime.builtin;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.enso.compiler.Passes;
import org.enso.compiler.context.FreshNameSupply;
import org.enso.compiler.exception.CompilerError;
import org.enso.compiler.phase.BuiltinsIrBuilder;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointMethodGen;
import org.enso.interpreter.node.expression.builtin.debug.DebugEvalMethodGen;
import org.enso.interpreter.node.expression.builtin.error.CatchErrorMethodGen;
import org.enso.interpreter.node.expression.builtin.error.CatchPanicMethodGen;
import org.enso.interpreter.node.expression.builtin.error.RecoverPanicMethodGen;
import org.enso.interpreter.node.expression.builtin.error.ThrowErrorMethodGen;
import org.enso.interpreter.node.expression.builtin.error.ThrowPanicMethodGen;
import org.enso.interpreter.node.expression.builtin.function.ApplicationOperatorMethodGen;
@ -29,6 +36,7 @@ import org.enso.pkg.QualifiedName;
/** Container class for static predefined atoms, methods, and their containing scope. */
public class Builtins {
public static final String SOURCE_NAME = "Builtins.enso";
public static final String MODULE_NAME = "Builtins.Main";
/** Container for method names needed outside this class. */
@ -40,7 +48,7 @@ public class Builtins {
private final AtomConstructor any;
private final AtomConstructor debug;
private final AtomConstructor ensoProject;
private final AtomConstructor projectDescription;
private final AtomConstructor function;
private final AtomConstructor nothing;
@ -70,8 +78,8 @@ public class Builtins {
any = new AtomConstructor("Any", scope).initializeFields();
bool = new Bool(language, scope);
debug = new AtomConstructor("Debug", scope).initializeFields();
ensoProject =
new AtomConstructor("Enso_Project", scope)
projectDescription =
new AtomConstructor("Project_Description", scope)
.initializeFields(
new ArgumentDefinition(
0, "prim_root_file", ArgumentDefinition.ExecutionMode.EXECUTE));
@ -116,7 +124,7 @@ public class Builtins {
scope.registerConstructor(error);
scope.registerConstructor(state);
scope.registerConstructor(debug);
scope.registerConstructor(ensoProject);
scope.registerConstructor(projectDescription);
scope.registerConstructor(runtime);
scope.registerConstructor(java);
@ -135,7 +143,7 @@ public class Builtins {
scope.registerMethod(runtime, "gc", GCMethodGen.makeFunction(language));
scope.registerMethod(panic, "throw", ThrowPanicMethodGen.makeFunction(language));
scope.registerMethod(panic, "recover", CatchPanicMethodGen.makeFunction(language));
scope.registerMethod(panic, "recover", RecoverPanicMethodGen.makeFunction(language));
scope.registerMethod(error, "throw", ThrowErrorMethodGen.makeFunction(language));
scope.registerMethod(any, "catch", CatchErrorMethodGen.makeFunction(language));
@ -158,8 +166,31 @@ public class Builtins {
thread, "with_interrupt_handler", WithInterruptHandlerMethodGen.makeFunction(language));
scope.registerMethod(unsafe, "set_atom_field", SetAtomFieldMethodGen.makeFunction(language));
}
module.unsafeBuildIrStub();
/** @return {@code true} if the IR has been initialized, otherwise {@code false} */
public boolean isIrInitialized() {
return this.module.getIr() != null;
}
/**
* Initialize the IR for the builtins module from the builtins source file.
*
* @param freshNameSupply the compiler's fresh name supply
* @param passes the passes manager for the compiler
*/
public void initializedBuiltinsIr(FreshNameSupply freshNameSupply, Passes passes) {
try {
var builtinsModuleBytes =
Objects.requireNonNull(
getClass().getClassLoader().getResourceAsStream(Builtins.SOURCE_NAME))
.readAllBytes();
String source = new String(builtinsModuleBytes, StandardCharsets.UTF_8);
module.setLiteralSource(source);
BuiltinsIrBuilder.build(module, freshNameSupply, passes);
} catch (IOException e) {
throw new CompilerError("Fatal, unable to read Builtins source file.");
}
}
/**
@ -227,8 +258,8 @@ public class Builtins {
}
/** @return the {@code Enso_Project} atom constructor */
public AtomConstructor getEnsoProject() {
return ensoProject;
public AtomConstructor getProjectDescription() {
return projectDescription;
}
/** @return the {@code System} atom constructor. */

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
package org.enso.compiler
import java.io.StringReader
import com.oracle.truffle.api.source.Source
import org.enso.compiler.codegen.{AstToIr, IrToTruffle, RuntimeStubsGenerator}
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
@ -19,6 +18,7 @@ import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
import org.enso.interpreter.runtime.Context
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
import org.enso.interpreter.runtime.Module
import org.enso.interpreter.runtime.builtin.Builtins
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
import org.enso.polyglot.LanguageInfo
import org.enso.syntax.text.Parser.IDMap
@ -32,7 +32,7 @@ import scala.jdk.OptionConverters._
*
* @param context the language context
*/
class Compiler(val context: Context) {
class Compiler(val context: Context, private val builtins: Builtins) {
private val freshNameSupply: FreshNameSupply = new FreshNameSupply
private val passes: Passes = new Passes
private val passManager: PassManager = passes.passManager
@ -40,6 +40,14 @@ class Compiler(val context: Context) {
private val stubsGenerator: RuntimeStubsGenerator =
new RuntimeStubsGenerator()
/** Lazy-initializes the IR for the builtins module.
*/
def initializeBuiltinsIr(): Unit = {
if (!builtins.isIrInitialized) {
builtins.initializedBuiltinsIr(freshNameSupply, passes)
}
}
/** Processes the provided language sources, registering any bindings in the
* given scope.
*

View File

@ -21,6 +21,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
val moduleDiscoveryPasses = new PassGroup(
List(
ModuleAnnotations,
DocumentationComments,
MainImportAndExport,
ComplexType,
@ -43,7 +44,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
IgnoredBindings,
TypeFunctions,
TypeSignatures,
Annotations,
ExpressionAnnotations,
AliasAnalysis,
UppercaseNames,
VectorLiterals,

View File

@ -10,7 +10,13 @@ import org.enso.compiler.core.IR.Name.MethodReference
import org.enso.compiler.core.IR._
import org.enso.compiler.exception.UnhandledEntity
import org.enso.syntax.text.AST
import org.enso.syntax.text.Shape.{SegmentEscape, SegmentExpr, SegmentPlain, SegmentRawEscape, TextUnclosed}
import org.enso.syntax.text.Shape.{
SegmentEscape,
SegmentExpr,
SegmentPlain,
SegmentRawEscape,
TextUnclosed
}
import org.enso.syntax.text.ast.text.Escape
import org.enso.syntax.text.ast.text.Escape.Unicode
@ -126,6 +132,8 @@ object AstToIr {
*/
def translateModuleSymbol(inputAst: AST): Module.Scope.Definition = {
inputAst match {
case AST.Ident.Annotation.any(annotation) =>
IR.Name.Annotation(annotation.name, getIdentifiedLocation(annotation))
case AstView.Atom(consName, args) =>
Module.Scope.Definition
.Atom(
@ -136,9 +144,9 @@ object AstToIr {
case AstView.TypeDef(typeName, args, body) =>
val translatedBody = translateTypeBody(body)
val containsAtomDefOrInclude = translatedBody.exists {
case _: IR.Module.Scope.Definition.Atom => true
case _: IR.Name.Literal => true
case _ => false
case _: IR.Module.Scope.Definition.Atom => true
case _: IR.Name.Literal => true
case _ => false
}
val hasArgs = args.nonEmpty
@ -271,6 +279,8 @@ object AstToIr {
.getOrElse(maybeParensedInput)
inputAst match {
case AST.Ident.Annotation.any(ann) =>
IR.Name.Annotation(ann.name, getIdentifiedLocation(ann))
case AST.Ident.Cons.any(include) => translateIdent(include)
case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs)

View File

@ -1,7 +1,5 @@
package org.enso.compiler.codegen
import java.math.BigInteger
import com.oracle.truffle.api.Truffle
import com.oracle.truffle.api.source.{Source, SourceSection}
import org.enso.compiler.core.IR
@ -19,7 +17,6 @@ import org.enso.compiler.pass.analyse.{
}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.resolve.{
Annotations,
MethodDefinitions,
Patterns,
UppercaseNames
@ -37,13 +34,7 @@ import org.enso.interpreter.node.callable.{
}
import org.enso.interpreter.node.controlflow.caseexpr._
import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode
import org.enso.interpreter.node.expression.constant.{
ConstantObjectNode,
ConstructorNode,
DynamicSymbolNode,
EnsoProjectNode,
ErrorNode
}
import org.enso.interpreter.node.expression.constant._
import org.enso.interpreter.node.expression.literal.{
BigIntegerLiteralNode,
DecimalLiteralNode,
@ -73,6 +64,7 @@ import org.enso.interpreter.runtime.error.DuplicateArgumentNameException
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
import org.enso.interpreter.{Constants, Language}
import java.math.BigInteger
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
@ -304,7 +296,7 @@ class IrToTruffle(
): BaseNode.TailStatus = {
val isTailPosition =
expression.getMetadata(TailCall).contains(TailCall.TailPosition.Tail)
val isTailAnnotated = expression.getMetadata(Annotations).isDefined
val isTailAnnotated = TailCall.isTailAnnotated(expression)
if (isTailPosition) {
if (isTailAnnotated) {
BaseNode.TailStatus.TAIL_LOOP

View File

@ -20,7 +20,8 @@ case class InlineContext(
localScope: Option[LocalScope] = None,
isInTailPosition: Option[Boolean] = None,
freshNameSupply: Option[FreshNameSupply] = None,
passConfiguration: Option[PassConfiguration] = None
passConfiguration: Option[PassConfiguration] = None,
noWarnings: Boolean = false
)
object InlineContext {

View File

@ -12,5 +12,6 @@ import org.enso.interpreter.runtime.Module
case class ModuleContext(
module: Module,
freshNameSupply: Option[FreshNameSupply] = None,
passConfiguration: Option[PassConfiguration] = None
passConfiguration: Option[PassConfiguration] = None,
noWarnings: Boolean = false
)

View File

@ -2093,7 +2093,9 @@ object IR {
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Name {
) extends Name
with IR.Module.Scope.Definition
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
@ -5433,10 +5435,10 @@ object IR {
/** An error coming from a tail call annotation placed in a syntactically
* incorrect position.
*/
case object UnexpectedTailCallAnnotation extends Reason {
case object UnexpectedAnnotation extends Reason {
override def explain(originalName: Name): String =
s"Unexpected @TailCall annotation. This annotation can only be " +
s"used with function applications."
s"Unexpected ${originalName.name} annotation. This annotation can " +
s"only be used with function applications."
}
/** An error coming from an unexpected occurence of a polyglot symbol.
@ -5699,7 +5701,8 @@ object IR {
s"$base is not a valid numeric base."
}
case class InvalidNumberForBase(base: String, number: String) extends Reason {
case class InvalidNumberForBase(base: String, number: String)
extends Reason {
override def explanation: String =
s"$number is not valid in $base."
}

View File

@ -175,6 +175,11 @@ case object AliasAnalysis extends IRPass {
"Type signatures should not exist at the top level during " +
"alias analysis."
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of alias " +
"analysis."
)
case err: IR.Error => err
}
}

View File

@ -117,6 +117,11 @@ case object CachePreferenceAnalysis extends IRPass {
"Type signatures should not exist at the top level during " +
"cache preference analysis."
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"cache preference analysis."
)
case err: IR.Error => err
}

View File

@ -126,6 +126,11 @@ case object DataflowAnalysis extends IRPass {
"Type signatures should not exist at the top level during " +
"dataflow analysis."
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"dataflow analysis."
)
case err: IR.Error => err
}
}

View File

@ -7,7 +7,7 @@ import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.Annotations
import org.enso.compiler.pass.resolve.ExpressionAnnotations
/** This pass performs tail call analysis on the Enso IR.
*
@ -114,6 +114,11 @@ case object TailCall extends IRPass {
"Type signatures should not exist at the top level during " +
"tail call analysis."
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"tail call analysis."
)
case err: IR.Error => err
}
}
@ -130,11 +135,8 @@ case object TailCall extends IRPass {
isInTailPosition: Boolean
): IR.Expression = {
val expressionWithWarning =
if (
expression
.getMetadata(Annotations)
.contains(Annotations.TailCallAnnotated) && !isInTailPosition
) expression.addDiagnostic(IR.Warning.WrongTco(expression.location))
if (isTailAnnotated(expression) && !isInTailPosition)
expression.addDiagnostic(IR.Warning.WrongTco(expression.location))
else expression
expressionWithWarning match {
case empty: IR.Empty =>
@ -480,4 +482,21 @@ case object TailCall extends IRPass {
tailPosition.isTail
}
}
/** Checks if the provided `expression` is annotated with a tail call
* annotation.
*
* @param expression the expression to check
* @return `true` if `expression` is annotated with `@Tail_Call`, otherwise
* `false`
*/
def isTailAnnotated(expression: IR.Expression): Boolean = {
expression
.getMetadata(ExpressionAnnotations)
.exists(anns =>
anns.annotations.exists(a =>
a.name == ExpressionAnnotations.tailCallName
)
)
}
}

View File

@ -20,7 +20,8 @@ import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
LambdaConsolidate
}
import org.enso.compiler.pass.resolve.IgnoredBindings
import org.enso.compiler.pass.resolve.{IgnoredBindings, ModuleAnnotations}
import org.enso.compiler.core.ir.MetadataStorage._
import scala.annotation.unused
@ -41,7 +42,7 @@ case object ComplexType extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = List()
override val precursorPasses: Seq[IRPass] = List(ModuleAnnotations)
override val invalidatedPasses: Seq[IRPass] =
List(
AliasAnalysis,
@ -102,12 +103,15 @@ case object ComplexType extends IRPass {
def desugarComplexType(
typ: IR.Module.Scope.Definition.Type
): List[IR.Module.Scope.Definition] = {
val atomDefs = typ.body.collect { case d: IR.Module.Scope.Definition.Atom =>
d
}
val atomIncludes = typ.body.collect { case n: IR.Name =>
n
}
val annotations = typ.getMetadata(ModuleAnnotations)
val atomDefs = typ.body
.collect { case d: IR.Module.Scope.Definition.Atom => d }
.map(atom =>
annotations
.map(ann => atom.updateMetadata(ModuleAnnotations -->> ann))
.getOrElse(atom)
)
val atomIncludes = typ.body.collect { case n: IR.Name => n }
val namesToDefineMethodsOn = atomIncludes ++ atomDefs.map(_.name)
val remainingEntities = typ.body.filterNot {

View File

@ -148,6 +148,11 @@ case object FunctionBinding extends IRPass {
"Documentation should not be present during function binding" +
"desugaring."
)
case _: IR.Name.Annotation =>
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
}

View File

@ -46,8 +46,9 @@ case object UnusedBindings extends IRPass {
override def runModule(
ir: IR.Module,
moduleContext: ModuleContext
): IR.Module =
): IR.Module = if (!moduleContext.noWarnings) {
ir.mapExpressions(runExpression(_, InlineContext(moduleContext.module)))
} else ir
/** Lints an arbitrary expression.
*
@ -60,12 +61,13 @@ case object UnusedBindings extends IRPass {
override def runExpression(
ir: IR.Expression,
inlineContext: InlineContext
): IR.Expression =
): IR.Expression = if (!inlineContext.noWarnings) {
ir.transformExpressions {
case binding: IR.Expression.Binding => lintBinding(binding, inlineContext)
case function: IR.Function => lintFunction(function, inlineContext)
case cse: IR.Case => lintCase(cse, inlineContext)
}
} else ir
// === Pass Internals =======================================================

View File

@ -4,6 +4,7 @@ import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Case.Branch
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{ComplexType, GenerateMethodBodies}
@ -140,6 +141,11 @@ case object DocumentationComments extends IRPass {
case doc: IR.Comment.Documentation => doc
case tySig: IR.Type.Ascription => tySig
case err: IR.Error => err
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"documentation comment resolution."
)
}
/** Resolves documentation comments in a module.

View File

@ -5,26 +5,25 @@ import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.MetadataStorage.ToPair
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.resolve.ModuleAnnotations.Annotations
case object Annotations extends IRPass {
case object TailCallAnnotated extends IRPass.Metadata {
override val metadataName: String = "TailCallAnnotated"
override def duplicate(): Option[IRPass.Metadata] = Some(this)
}
val tailCallName = "@Tail_Call"
case object ExpressionAnnotations extends IRPass {
val tailCallName = "@Tail_Call"
val builtinMethodName = "@Builtin_Method"
val knownAnnotations = Seq(tailCallName, builtinMethodName)
/** The type of the metadata object that the pass writes to the IR. */
override type Metadata = TailCallAnnotated.type
override type Metadata = Annotations
/** The type of configuration for the pass. */
override type Config = IRPass.Configuration.Default
/** The passes that this pass depends _directly_ on to run. */
override val precursorPasses: Seq[IRPass] = Seq()
override val precursorPasses: Seq[IRPass] = Seq(ModuleAnnotations)
/** The passes that are invalidated by running this pass. */
override val invalidatedPasses: Seq[IRPass] = Seq()
override val invalidatedPasses: Seq[IRPass] = Seq(AliasAnalysis)
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir`.
@ -70,7 +69,7 @@ case object Annotations extends IRPass {
_,
_
) =>
if (ann.name == tailCallName) {
if (isKnownAnnotation(ann.name)) {
arguments match {
case List() =>
throw new CompilerError(
@ -78,13 +77,13 @@ case object Annotations extends IRPass {
)
case List(arg) =>
doExpression(arg.value)
.updateMetadata(this -->> TailCallAnnotated)
.updateMetadata(this -->> Annotations(Seq(ann)))
case realFun :: args =>
val recurFun = doExpression(realFun.value)
val recurArgs = args.map(_.mapExpressions(doExpression))
app
.copy(function = recurFun, arguments = recurArgs)
.updateMetadata(this -->> TailCallAnnotated)
.updateMetadata(this -->> Annotations(Seq(ann)))
}
} else {
val err =
@ -92,14 +91,22 @@ case object Annotations extends IRPass {
app.copy(function = err)
}
case ann: IR.Name.Annotation =>
if (ann.name == tailCallName) {
if (isKnownAnnotation(ann.name)) {
IR.Error.Resolution(
ann,
IR.Error.Resolution.UnexpectedTailCallAnnotation
IR.Error.Resolution.UnexpectedAnnotation
)
} else {
IR.Error.Resolution(ann, IR.Error.Resolution.UnknownAnnotation)
}
}
/** Checks if `name` is a known annotation.
*
* @param name the annotation name to check
* @return `true` if `name` is a known annotation, otherwise `false`
*/
def isKnownAnnotation(name: String): Boolean = {
knownAnnotations.contains(name)
}
}

View File

@ -0,0 +1,123 @@
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.Name
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{
ComplexType,
FunctionBinding,
GenerateMethodBodies
}
import scala.annotation.unused
/** A pass responsible for the discovery of module annotations, and for
* associating them with the corresponding construct.
*/
case object ModuleAnnotations extends IRPass {
override type Metadata = Annotations
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = Seq()
override val invalidatedPasses: Seq[IRPass] = Seq(
DocumentationComments,
ComplexType,
FunctionBinding,
GenerateMethodBodies
)
/** Resolves module-level 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.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.Type =>
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
}
}
ir.copy(bindings = newBindings.flatten)
}
/** Resolves top level annotations within a complex type.
*
* @param typ the type in which to resolve annotations
* @return `typ` with all top-level annotations resolved
*/
def resolveComplexType(typ: Definition.Type): Definition.Type = {
var lastAnnotations: Seq[IR.Name.Annotation] = Seq()
val newBodyElems = typ.body.flatMap {
case ann: Name.Annotation =>
lastAnnotations :+= ann
None
case comment: IR.Comment => Some(comment)
case entity =>
val res = Some(
entity.updateMetadata(this -->> Annotations(lastAnnotations))
)
lastAnnotations = Seq()
res
}
typ.copy(body = newBodyElems)
}
/** 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,
@unused inlineContext: InlineContext
): IR.Expression = ir
/** A container for annotations on an IR construct.
*
* @param annotations the initial annotations for the container
*/
case class Annotations(annotations: Seq[IR.Name.Annotation])
extends IRPass.Metadata {
override val metadataName: String = "Annotations"
override def duplicate(): Option[IRPass.Metadata] = Some(this.copy())
/** Add an annotation to the annotations container.
*
* @param annotation the annotation to add
* @return `this`, with `annotation` added to it
*/
def addAnnotation(annotation: IR.Name.Annotation): Annotations =
this.copy(annotations = this.annotations :+ annotation)
}
}

View File

@ -139,6 +139,11 @@ 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 =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"suspended arguments analysis."
)
}
}

View File

@ -26,7 +26,8 @@ case object TypeSignatures extends IRPass {
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = List(
TypeFunctions
TypeFunctions,
ModuleAnnotations,
)
override val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
@ -93,21 +94,34 @@ case object TypeSignatures extends IRPass {
newMethod.updateMetadata(DocumentationComments -->> doc)
)
.getOrElse(newMethod)
val newMethodWithAnnotations = asc
.getMetadata(ModuleAnnotations)
.map(annotations =>
newMethodWithDoc.updateMetadata(
ModuleAnnotations -->> annotations
)
)
.getOrElse(newMethodWithDoc)
typed match {
case ref: IR.Name.MethodReference =>
if (ref isSameReferenceAs methodRef) {
Some(
newMethodWithDoc.updateMetadata(this -->> Signature(sig))
newMethodWithAnnotations.updateMetadata(
this -->> Signature(sig)
)
)
} else {
List(
IR.Error.Unexpected.TypeSignature(asc),
newMethodWithDoc
newMethodWithAnnotations
)
}
case _ =>
List(IR.Error.Unexpected.TypeSignature(asc), newMethodWithDoc)
List(
IR.Error.Unexpected.TypeSignature(asc),
newMethodWithAnnotations
)
}
case None => Some(newMethod)
}
@ -122,6 +136,11 @@ case object TypeSignatures extends IRPass {
"Complex type definitions should not be present during type " +
"signature resolution."
)
case _: IR.Name.Annotation =>
throw new CompilerError(
"Annotations should already be associated by the point of " +
"type signature resolution."
)
case _: IR.Comment.Documentation =>
throw new CompilerError(
"Documentation comments should not be present during type " +

View File

@ -0,0 +1,61 @@
package org.enso.compiler.phase
import org.enso.compiler.Passes
import org.enso.compiler.codegen.AstToIr
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
import org.enso.interpreter.runtime.Module
import org.enso.interpreter.runtime.Module.CompilationStage
import org.enso.syntax.text.Parser
import scala.annotation.unused
/** A phase responsible for initializing the builtins' IR from the provided
* source.
*/
object BuiltinsIrBuilder {
/** Builds the IR for the builtins module based on the builtins source file.
*
* We guarantee that the builtins file neither imports anything or restricts
* any exports, and are hence safe to unconditionally run most of the compiler
* pipeline. We do not want to run codegen, however, as these definitions
* would conflict with the existing builtins.
*
* This is kept as a separate flow as it is independent of the true
* compilation pipeline
*
* @param module the module to build the IR for
* @param freshNameSupply the compiler's fresh name supply
* @param passes the compiler's pass manager
*/
def build(
@unused module: Module,
@unused freshNameSupply: FreshNameSupply,
@unused passes: Passes
): Unit = {
val passManager = passes.passManager
val moduleContext = ModuleContext(
module = module,
freshNameSupply = Some(freshNameSupply),
noWarnings = true
)
val parsedAst = Parser().runWithIds(module.getSource.getCharacters.toString)
val initialIr = AstToIr.translate(parsedAst)
val irAfterModDiscovery = passManager.runPassesOnModule(
initialIr,
moduleContext,
passes.moduleDiscoveryPasses
)
module.unsafeSetIr(irAfterModDiscovery)
module.unsafeSetCompilationStage(Module.CompilationStage.AFTER_PARSING)
new ExportsResolution().run(List(module))
val irAfterCompilation = passManager.runPassesOnModule(
irAfterModDiscovery,
moduleContext,
passes.functionBodyPasses
)
module.unsafeSetIr(irAfterCompilation)
module.unsafeSetCompilationStage(CompilationStage.AFTER_CODEGEN)
}
}

View File

@ -1,11 +1,11 @@
package org.enso.compiler.phase
import org.enso.compiler.core.IR
import org.enso.interpreter.runtime.Module
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{ResolvedConstructor, ResolvedMethod}
import org.enso.compiler.pass.analyse.BindingAnalysis
import org.enso.interpreter.runtime.Module
import scala.jdk.CollectionConverters._

View File

@ -8,15 +8,7 @@ import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.lint.ShadowedPatternFields
import org.enso.compiler.pass.optimise.UnreachableMatchBranches
import org.enso.compiler.pass.resolve.{
Annotations,
DocumentationComments,
IgnoredBindings,
MethodDefinitions,
ModuleThisToHere,
TypeFunctions,
TypeSignatures
}
import org.enso.compiler.pass.resolve.{DocumentationComments, ExpressionAnnotations, IgnoredBindings, MethodDefinitions, ModuleAnnotations, ModuleThisToHere, TypeFunctions, TypeSignatures}
class PassesTest extends CompilerTest {
@ -48,6 +40,7 @@ class PassesTest extends CompilerTest {
"get the precursors of a given pass" in {
passes.getPrecursors(AliasAnalysis).map(_.passes) shouldEqual Some(
List(
ModuleAnnotations,
DocumentationComments,
MainImportAndExport,
ComplexType,
@ -65,7 +58,7 @@ class PassesTest extends CompilerTest {
IgnoredBindings,
TypeFunctions,
TypeSignatures,
Annotations
ExpressionAnnotations
)
)
}

View File

@ -852,38 +852,70 @@ class AstToIrTest extends CompilerTest with Inside {
}
}
"properly support different kinds of imports" in {
val imports = List(
"import Foo.Bar as Baz",
"import Foo.Bar",
"from Foo.Bar import Baz",
"from Foo.Bar import Baz, Spam",
"from Foo.Bar import all",
"from Foo.Bar as Eggs import all hiding Spam",
"from Foo.Bar import all hiding Spam, Eggs"
)
imports
.mkString("\n")
.toIrModule
.imports
.map(_.showCode()) shouldEqual imports
"AST translation of top-level annotations" should {
"support annotations at the top level" in {
val ir =
"""@My_Annotation
|type Foo a b
|""".stripMargin.toIrModule
ir.bindings.head shouldBe an[IR.Name.Annotation]
ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Atom]
}
"support annotations inside complex type bodies" in {
val ir =
"""type My_Type
| @My_Annotation
| type Foo
|
| @My_Annotation
| add a = this + a
|""".stripMargin.toIrModule
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Type]
val complexType =
ir.bindings.head.asInstanceOf[IR.Module.Scope.Definition.Type]
complexType.body.head shouldBe an[IR.Name.Annotation]
complexType.body(2) shouldBe an[IR.Name.Annotation]
}
}
"properly support different kinds of exports" in {
val exports = List(
"export Foo.Bar as Baz",
"export Foo.Bar",
"from Foo.Bar export Baz",
"from Foo.Bar export baz, Spam",
"from Foo.Bar export all",
"from Foo.Bar as Eggs export all hiding Spam",
"from Foo.Bar export all hiding Spam, eggs"
)
exports
.mkString("\n")
.toIrModule
.exports
.map(_.showCode()) shouldEqual exports
"AST translation for imports and exports" should {
"properly support different kinds of imports" in {
val imports = List(
"import Foo.Bar as Baz",
"import Foo.Bar",
"from Foo.Bar import Baz",
"from Foo.Bar import Baz, Spam",
"from Foo.Bar import all",
"from Foo.Bar as Eggs import all hiding Spam",
"from Foo.Bar import all hiding Spam, Eggs"
)
imports
.mkString("\n")
.toIrModule
.imports
.map(_.showCode()) shouldEqual imports
}
"properly support different kinds of exports" in {
val exports = List(
"export Foo.Bar as Baz",
"export Foo.Bar",
"from Foo.Bar export Baz",
"from Foo.Bar export baz, Spam",
"from Foo.Bar export all",
"from Foo.Bar as Eggs export all hiding Spam",
"from Foo.Bar export all hiding Spam, eggs"
)
exports
.mkString("\n")
.toIrModule
.exports
.map(_.showCode()) shouldEqual exports
}
}
"AST translation of erroneous constructs" should {

View File

@ -5,6 +5,7 @@ import org.enso.compiler.context.ModuleContext
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition
import org.enso.compiler.pass.desugar.ComplexType
import org.enso.compiler.pass.resolve.ModuleAnnotations
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
@ -79,11 +80,28 @@ class ComplexTypeTest extends CompilerTest {
| type Bar
|""".stripMargin.preprocessModule.desugar
exactly(2, ir.bindings) shouldBe an[Definition.Atom]
exactly(2, ir.bindings) shouldBe a[Definition.Atom]
ir.bindings.head.asInstanceOf[Definition.Atom].name.name shouldEqual "Foo"
ir.bindings(1).asInstanceOf[Definition.Atom].name.name shouldEqual "Bar"
}
"have annotations on the type desugared to annotations on the defined" in {
val ir =
"""@Builtin_Type
|type My_Type
| Foo
| type Bar
|""".stripMargin.preprocessModule.desugar
exactly(1, ir.bindings) shouldBe a[Definition.Atom]
ir.bindings.head
.asInstanceOf[Definition.Atom]
.unsafeGetMetadata(ModuleAnnotations, "")
.annotations
.head
.name shouldEqual "@Builtin_Type"
}
"have their methods desugared to methods on included atoms" in {
ir.bindings(3) shouldBe an[Definition.Method.Binding]
val justIsJust = ir.bindings(3).asInstanceOf[Definition.Method.Binding]

View File

@ -1,13 +1,13 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.resolve.Annotations
import org.enso.compiler.pass.resolve.ExpressionAnnotations
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
class AnnotationsTest extends CompilerTest {
class ExpressionAnnotationsTest extends CompilerTest {
// === Test Setup ===========================================================
@ -16,10 +16,15 @@ class AnnotationsTest extends CompilerTest {
freshNameSupply = Some(new FreshNameSupply)
)
def mkInlineContext: InlineContext =
buildInlineContext(
freshNameSupply = Some(new FreshNameSupply)
)
val passes = new Passes
val precursorPasses: PassGroup =
passes.getPrecursors(Annotations).get
passes.getPrecursors(ExpressionAnnotations).get
val passConfiguration: PassConfiguration = PassConfiguration()
@ -38,7 +43,13 @@ class AnnotationsTest extends CompilerTest {
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: ModuleContext): IR.Module = {
Annotations.runModule(ir, context)
ExpressionAnnotations.runModule(ir, context)
}
}
implicit class AnalyseExpression(ir: IR.Expression) {
def analyse(implicit context: InlineContext): IR.Expression = {
ExpressionAnnotations.runExpression(ir, context)
}
}
@ -52,39 +63,44 @@ class AnnotationsTest extends CompilerTest {
|foo x =
| @Tail_Call
| @Unknown_Annotation foo bar baz
| @Builtin_Method "myBuiltin"
| foo @Tail_Call
| foo (@Tail_Call bar baz)
|""".stripMargin.preprocessModule.analyse
"resolve and mark annotations" in {
val items = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
val items = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
"create an error when discovering an unexpected annotation" in {
items.expressions(0) shouldBe an[IR.Error.Resolution]
items
.expressions(0)
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnexpectedTailCallAnnotation
.reason shouldEqual IR.Error.Resolution.UnexpectedAnnotation
}
"create an error when discovering an unknown annotation" in {
val unknown =
items.expressions(1).asInstanceOf[IR.Application.Prefix].function
unknown shouldBe an[IR.Error.Resolution]
unknown
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnknownAnnotation
}
val misplaced = items
.expressions(2)
.asInstanceOf[IR.Application.Prefix]
.arguments(0)
.value
misplaced shouldBe an[IR.Error.Resolution]
misplaced
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnexpectedTailCallAnnotation
"associate the annotation with the annotated definition" in {
val correctDef = items.expressions(2).asInstanceOf[IR.Literal.Text]
correctDef.text shouldEqual "myBuiltin"
correctDef
.unsafeGetMetadata(ExpressionAnnotations, "")
.annotations
.head
.name shouldEqual "@Builtin_Method"
val correct = items.returnValue
.asInstanceOf[IR.Application.Prefix]
@ -93,9 +109,24 @@ class AnnotationsTest extends CompilerTest {
.asInstanceOf[IR.Application.Prefix]
correct.function.asInstanceOf[IR.Name].name shouldEqual "bar"
correct.arguments.length shouldEqual 1
correct.getMetadata(Annotations) should contain(
Annotations.TailCallAnnotated
)
correct
.getMetadata(ExpressionAnnotations)
.get
.annotations
.head
.name shouldEqual "@Tail_Call"
}
"create an error on a misplaced annotation" in {
val misplaced = items
.expressions(3)
.asInstanceOf[IR.Application.Prefix]
.arguments(0)
.value
misplaced shouldBe an[IR.Error.Resolution]
misplaced
.asInstanceOf[IR.Error.Resolution]
.reason shouldEqual IR.Error.Resolution.UnexpectedAnnotation
}
}
}

View File

@ -0,0 +1,191 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
import org.enso.compiler.context.ModuleContext
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.pass.resolve.ModuleAnnotations
import org.enso.compiler.test.CompilerTest
class ModuleAnnotationsTest extends CompilerTest {
// === Test Setup ===========================================================
val passes = new Passes
val precursorPasses: PassGroup = passes.getPrecursors(ModuleAnnotations).get
val passConfiguration: PassConfiguration = PassConfiguration();
implicit val passManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
/** Adds an extension method for running module-level annotations resolution
* on the input IR.
*
* @param ir the IR to resolve
*/
implicit class ResolveModule(ir: IR.Module) {
/** Runs annotation resolution on an [[IR.Module]].
*
* @param moduleContext the module context in which the resolution takes
* place
* @return [[ir]], with all module-level annotations resolved
*/
def resolve(implicit moduleContext: ModuleContext): IR.Module = {
ModuleAnnotations.runModule(ir, moduleContext)
}
}
/** Makes a module context.
*
* @return a fresh module context
*/
def mkModuleContext: ModuleContext = {
buildModuleContext()
}
// === The Tests ============================================================
"Annotation desugaring at the top level" should {
implicit val moduleContext: ModuleContext = mkModuleContext
"associate annotations with atom definitions" in {
val ir =
"""@My_Annotation_1
|@My_Annotation_2
|type My_Atom
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Atom]
val anns =
ir.bindings.head.unsafeGetMetadata(ModuleAnnotations, "").annotations
anns.length shouldEqual 2
anns.head.asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_1"
anns(1).asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_2"
}
"associate annotations with complex type definitions" in {
val ir =
"""@My_Annotation_1
|@My_Annotation_2
|type Foo
| type Bar
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Type]
val anns =
ir.bindings.head.unsafeGetMetadata(ModuleAnnotations, "").annotations
anns.length shouldEqual 2
anns.head.asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_1"
anns(1).asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_2"
}
"associate annotations with method definitions" in {
val ir =
"""@My_Annotation_1
|@My_Annotation_2
|My_Type.add a b = this.frob(a + b)
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Method.Binding]
val anns =
ir.bindings.head.unsafeGetMetadata(ModuleAnnotations, "").annotations
anns.length shouldEqual 2
anns.head.asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_1"
anns(1).asInstanceOf[IR.Name].name shouldEqual "@My_Annotation_2"
}
"not associate annotations with comments" in {
val ir =
"""@My_Annotation
|## My doc comment
|type Foo
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 2
ir.bindings(1) shouldBe a[Definition.Atom]
val anns =
ir.bindings(1).unsafeGetMetadata(ModuleAnnotations, "").annotations
anns.length shouldEqual 1
anns.head.asInstanceOf[IR.Name].name shouldEqual "@My_Annotation"
}
}
"Annotation desugaring in complex types" should {
implicit val moduleContext: ModuleContext = mkModuleContext
"associate annotations with atom definitions" in {
val ir =
"""@My_Annotation
|type Foo
| @My_Annotation
| type Bar
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Type]
val typ = ir.bindings.head.asInstanceOf[Definition.Type]
typ.body.length shouldEqual 1
typ.body.head shouldBe a[Definition.Atom]
typ.body.head
.unsafeGetMetadata(ModuleAnnotations, "")
.annotations
.head
.asInstanceOf[IR.Name]
.name shouldEqual "@My_Annotation"
}
"associate annotations with method definitions" in {
val ir =
"""type Foo
| type Foo
|
| @My_Annotation
| my_method a = a
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Type]
val typ = ir.bindings.head.asInstanceOf[Definition.Type]
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"
}
"not associate annotations with comments" in {
val ir =
"""
|type Foo
| @My_Annotation
| ## Doc comment
| type Foo
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head shouldBe a[Definition.Type]
val typ = ir.bindings.head.asInstanceOf[Definition.Type]
typ.body.length shouldEqual 2
typ.body.head shouldBe an[IR.Comment]
typ.body(1) shouldBe a[Definition.Atom]
typ
.body(1)
.unsafeGetMetadata(ModuleAnnotations, "")
.annotations
.head
.asInstanceOf[IR.Name]
.name shouldEqual "@My_Annotation"
}
}
}

View File

@ -4,7 +4,11 @@ import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.pass.resolve.{DocumentationComments, TypeSignatures}
import org.enso.compiler.pass.resolve.{
DocumentationComments,
ModuleAnnotations,
TypeSignatures
}
import org.enso.compiler.test.CompilerTest
class TypeSignaturesTest extends CompilerTest {
@ -86,7 +90,6 @@ class TypeSignaturesTest extends CompilerTest {
ir.bindings.head.getMetadata(TypeSignatures) shouldBe defined
}
// TODO [AA] This isn't consistent.
"allow dotted paths in type signatures" in {
val ir =
"""
@ -96,7 +99,6 @@ class TypeSignaturesTest extends CompilerTest {
ir.bindings.length shouldEqual 1
ir.bindings.head.getMetadata(TypeSignatures) shouldBe defined
// println(ir.bindings.head.getMetadata(TypeSignatures).get.signature.pretty)
}
"raise an error if a signature is divorced from its definition" in {
@ -132,6 +134,18 @@ class TypeSignaturesTest extends CompilerTest {
ir.bindings.head.getMetadata(DocumentationComments) shouldBe defined
}
"reattach annotations to method definitions" in {
val ir =
"""@Builtin_Type
|bar : Number -> Number -> Number
|bar a b = a + b
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head.getMetadata(TypeSignatures) shouldBe defined
ir.bindings.head.getMetadata(ModuleAnnotations) shouldBe defined
}
"work inside type definition bodies" in {
val ir =
"""

View File

@ -1,13 +1,5 @@
package org.enso.interpreter.test
import java.io.{
ByteArrayOutputStream,
PipedInputStream,
PipedOutputStream,
PrintStream
}
import java.util.UUID
import com.oracle.truffle.api.instrumentation.EventBinding
import org.enso.interpreter.test.CodeIdsTestInstrument.IdEventListener
import org.enso.interpreter.test.CodeLocationsTestInstrument.LocationsEventListener
@ -27,6 +19,14 @@ import org.graalvm.polyglot.{Context, Value}
import org.scalatest.Assertions
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import java.io.{
ByteArrayOutputStream,
PipedInputStream,
PipedOutputStream,
PrintStream
}
import java.util.UUID
case class LocationsInstrumenter(instrument: CodeLocationsTestInstrument) {
var bindings: List[EventBinding[LocationsEventListener]] = List()
@ -235,7 +235,7 @@ trait InterpreterBehavior {
def specify(implicit interpreterContext: InterpreterContext): Unit
def contextModifiers: Context#Builder => Context#Builder = bldr => bldr
def contextModifiers: Option[Context#Builder => Context#Builder] = None
}
trait InterpreterTest
@ -247,18 +247,35 @@ trait InterpreterTest
subject when {
"Context is Cached" should {
behave like specify(new InterpreterContext(contextModifiers))
behave like specify(contextModifiers match {
case Some(mods) => new InterpreterContext(mods)
case None => GlobalContexts.cachedContext
})
}
}
subject when {
"Context is Uncached" should {
behave like specify(
new InterpreterContext(
contextModifiers
.andThen(_.option(RuntimeOptions.DISABLE_INLINE_CACHES, "true"))
)
contextModifiers match {
case Some(mods) =>
new InterpreterContext(
mods.andThen(
_.option(RuntimeOptions.DISABLE_INLINE_CACHES, "true")
)
)
case None => GlobalContexts.uncachedContext
}
)
}
}
}
object GlobalContexts {
val defaultMods: Context#Builder => Context#Builder = bldr => bldr
val cachedContext =
new InterpreterContext(defaultMods)
val uncachedContext = new InterpreterContext(
_.option(RuntimeOptions.DISABLE_INLINE_CACHES, "true")
)
}

View File

@ -9,8 +9,8 @@ class ReplTest extends InterpreterTest with BeforeAndAfter with EitherValues {
override def subject: String = "Repl"
override def contextModifiers: Context#Builder => Context#Builder =
_.option(DebugServerInfo.ENABLE_OPTION, "true")
override def contextModifiers: Option[Context#Builder => Context#Builder] =
Some(_.option(DebugServerInfo.ENABLE_OPTION, "true"))
override def specify(implicit
interpreterContext: InterpreterContext

View File

@ -7,7 +7,7 @@ class ImportsTest extends PackageTest {
evalTestProject("TestSimpleImports") shouldEqual 20
}
"Methods defined together with atom" should "be visible even if not imported" in {
"Methods defined together with atoms" should "be visible even if not imported" in {
evalTestProject("TestNonImportedOwnMethods") shouldEqual 10
}

View File

@ -11,8 +11,8 @@ import org.graalvm.polyglot.Context
class OverloadsResolutionErrorTest extends InterpreterTest {
override def subject: String = "Symbol Overloads"
override def contextModifiers: Context#Builder => Context#Builder =
_.option(RuntimeOptions.STRICT_ERRORS, "true")
override def contextModifiers: Option[Context#Builder => Context#Builder] =
Some(_.option(RuntimeOptions.STRICT_ERRORS, "true"))
override def specify(implicit
interpreterContext: InterpreterContext

View File

@ -11,8 +11,8 @@ import org.graalvm.polyglot.Context
class StrictCompileDiagnosticsTest extends InterpreterTest {
override def subject: String = "Compile Errors in Batch Mode"
override def contextModifiers: Context#Builder => Context#Builder =
_.option(RuntimeOptions.STRICT_ERRORS, "true")
override def contextModifiers: Option[Context#Builder => Context#Builder] =
Some(_.option(RuntimeOptions.STRICT_ERRORS, "true"))
override def specify(implicit
interpreterContext: InterpreterContext

View File

@ -191,7 +191,7 @@ object Application {
prettyName: String,
helpHeader: String,
commands: NonEmptyList[Command[Unit => Int]]
): Application[()] =
): Application[Unit] =
new Application(
commandName,
prettyName,

View File

@ -92,6 +92,7 @@ object Builtin {
}
case _ => internalError
}
case _ => internalError
}
}
@ -195,6 +196,7 @@ object Builtin {
AST.App.Infix(l.wrapped, Opr("->"), r.wrapped)
case _ => internalError
}
case _ => internalError
}
}

View File

@ -25,12 +25,6 @@ spec = describe "Pattern Matches" <|
case Integer of
Integer -> Nothing
_ -> Test.fail "Expected the Integer constructor to match."
case Big_Integer of
Integer -> Nothing
_ -> Test.fail "Expected the Big_Integer constructor to match."
case Small_Integer of
Integer -> Nothing
_ -> Test.fail "Expected the Small_Integer constructor to match."
it "should be able to match on the Decimal type" <|
case 1.7 of
Decimal -> Nothing
@ -51,12 +45,6 @@ spec = describe "Pattern Matches" <|
case Number of
Number -> Nothing
_ -> Test.fail "Expected the Number constructor to match."
case Small_Integer of
Number -> Nothing
_ -> Test.fail "Expected the Small_Integer constructor to match."
case Big_Integer of
Number -> Nothing
_ -> Test.fail "Expected the Big_Integer constructor to match."
case Integer of
Number -> Nothing
_ -> Test.fail "Expected the Integer constructor to match."