mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
Add sections, underscore args and ignores (#716)
This commit is contained in:
parent
55486e50d3
commit
ea23cf6fbc
@ -76,6 +76,8 @@ val jsSettings = Seq(
|
||||
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }
|
||||
)
|
||||
|
||||
scalacOptions in (Compile, console) ~= (_ filterNot (_ == "-Xfatal-warnings"))
|
||||
|
||||
// ============================================================================
|
||||
// === Benchmark Configuration ================================================
|
||||
// ============================================================================
|
||||
|
@ -323,6 +323,9 @@ Two typesets `A` and `B` are defined to be structurally equal if `A <: B` and
|
||||
> - Reformulate this in terms of row polymorphism. We really want to avoid a
|
||||
> _real_ subtyping relationship as it doesn't play at all well with global
|
||||
> inference.
|
||||
> - To that end, it is an open question as to whether we can have type unions
|
||||
> without subtyping. Conventionally we wouldn't be able to, but with our
|
||||
> theory we may.
|
||||
|
||||
#### Unsafe Typeset Field Mutation
|
||||
For performance it is sometimes necessary to have the ability to _directly_
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.enso.interpreter.runtime.error;
|
||||
|
||||
import com.oracle.truffle.api.TruffleException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
|
||||
/** An exception thrown when the program tries to redefine an already-defined method */
|
||||
public class RedefinedMethodException extends RuntimeException {
|
||||
public class RedefinedMethodException extends RuntimeException implements TruffleException {
|
||||
|
||||
/**
|
||||
* Creates a new error.
|
||||
@ -12,4 +15,13 @@ public class RedefinedMethodException extends RuntimeException {
|
||||
public RedefinedMethodException(String atom, String method) {
|
||||
super("Methods cannot be overloaded, but you have tried to overload " + atom + "." + method);
|
||||
}
|
||||
|
||||
/** Gets the location where the exception occurred.
|
||||
*
|
||||
* @return the location where the exception occurred
|
||||
*/
|
||||
@Override
|
||||
public Node getLocation() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,19 @@ public class ModuleScope {
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a constructor in the module scope locally.
|
||||
*
|
||||
* @param name the name of the module binding
|
||||
* @return the atom constructor associated with {@code name}, or {@link Optional#empty()}
|
||||
*/
|
||||
public Optional<AtomConstructor> getLocalConstructor(String name) {
|
||||
if (associatedType.getName().equals(name)) {
|
||||
return Optional.of(associatedType);
|
||||
}
|
||||
return Optional.ofNullable(this.constructors.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a constructor in the module scope.
|
||||
*
|
||||
@ -56,13 +69,10 @@ public class ModuleScope {
|
||||
* @return the Atom constructor associated with {@code name}, or {@link Optional#empty()}
|
||||
*/
|
||||
public Optional<AtomConstructor> getConstructor(String name) {
|
||||
if (associatedType.getName().equals(name)) {
|
||||
return Optional.of(associatedType);
|
||||
}
|
||||
Optional<AtomConstructor> locallyDefined = Optional.ofNullable(this.constructors.get(name));
|
||||
Optional<AtomConstructor> locallyDefined = getLocalConstructor(name);
|
||||
if (locallyDefined.isPresent()) return locallyDefined;
|
||||
return imports.stream()
|
||||
.map(scope -> scope.getConstructor(name))
|
||||
.map(scope -> scope.getLocalConstructor(name))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
|
@ -4,19 +4,17 @@ import java.io.StringReader
|
||||
|
||||
import com.oracle.truffle.api.TruffleFile
|
||||
import com.oracle.truffle.api.source.Source
|
||||
import org.enso.compiler.codegen.{AstToIR, IRToTruffle}
|
||||
import org.enso.compiler.codegen.{AstToIr, IRToTruffle}
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.{Expression, Module}
|
||||
import org.enso.compiler.exception.{CompilationAbortedException, CompilerError}
|
||||
import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse._
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
}
|
||||
import org.enso.compiler.pass.optimise.LambdaConsolidate
|
||||
import org.enso.compiler.pass.desugar._
|
||||
import org.enso.compiler.pass.lint.UnusedBindings
|
||||
import org.enso.compiler.pass.optimise._
|
||||
import org.enso.compiler.pass.resolve._
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.interpreter.Language
|
||||
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
|
||||
@ -51,15 +49,19 @@ class Compiler(
|
||||
*/
|
||||
val compilerPhaseOrdering: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda,
|
||||
IgnoredBindings,
|
||||
AliasAnalysis,
|
||||
LambdaConsolidate,
|
||||
OverloadsResolution,
|
||||
AliasAnalysis,
|
||||
DemandAnalysis,
|
||||
ApplicationSaturation,
|
||||
TailCall,
|
||||
DataflowAnalysis
|
||||
DataflowAnalysis,
|
||||
UnusedBindings
|
||||
)
|
||||
|
||||
/** Configuration for the passes. */
|
||||
@ -168,7 +170,7 @@ class Compiler(
|
||||
* @return an IR representation of the program represented by `sourceAST`
|
||||
*/
|
||||
def generateIR(sourceAST: AST): Module =
|
||||
AstToIR.translate(sourceAST)
|
||||
AstToIr.translate(sourceAST)
|
||||
|
||||
/**
|
||||
* Lowers the input AST to the compiler's high-level intermediate
|
||||
@ -178,7 +180,7 @@ class Compiler(
|
||||
* @return an IR representation of the program represented by `sourceAST`
|
||||
*/
|
||||
def generateIRInline(sourceAST: AST): Option[Expression] =
|
||||
AstToIR.translateInline(sourceAST)
|
||||
AstToIr.translateInline(sourceAST)
|
||||
|
||||
/** Runs the various compiler passes.
|
||||
*
|
||||
|
@ -2,6 +2,7 @@ package org.enso.compiler.codegen
|
||||
|
||||
import cats.Foldable
|
||||
import cats.implicits._
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR._
|
||||
import org.enso.compiler.exception.UnhandledEntity
|
||||
import org.enso.interpreter.Constants
|
||||
@ -15,7 +16,7 @@ import org.enso.syntax.text.AST
|
||||
* [[Core]] becomes implemented. Most function docs will refer to [[Core]]
|
||||
* now, as this will become true soon.
|
||||
*/
|
||||
object AstToIR {
|
||||
object AstToIr {
|
||||
private def getIdentifiedLocation(ast: AST): Option[IdentifiedLocation] =
|
||||
ast.location.map(IdentifiedLocation(_, ast.id))
|
||||
|
||||
@ -300,24 +301,36 @@ object AstToIR {
|
||||
): DefinitionArgument = {
|
||||
arg match {
|
||||
case AstView.LazyAssignedArgumentDefinition(name, value) =>
|
||||
translateIdent(name) match {
|
||||
case name: IR.Name =>
|
||||
DefinitionArgument.Specified(
|
||||
Name.Literal(name.name, getIdentifiedLocation(name)),
|
||||
name,
|
||||
Some(translateExpression(value)),
|
||||
suspended = true,
|
||||
getIdentifiedLocation(arg)
|
||||
)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(arg, "translateArgumentDefinition")
|
||||
}
|
||||
case AstView.LazyArgument(arg) =>
|
||||
translateArgumentDefinition(arg, isSuspended = true)
|
||||
case AstView.DefinitionArgument(arg) =>
|
||||
translateIdent(arg) match {
|
||||
case name: IR.Name =>
|
||||
DefinitionArgument.Specified(
|
||||
Name.Literal(arg.name, getIdentifiedLocation(arg)),
|
||||
name,
|
||||
None,
|
||||
isSuspended,
|
||||
getIdentifiedLocation(arg)
|
||||
)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(arg, "translateArgumentDefinition")
|
||||
}
|
||||
case AstView.AssignedArgument(name, value) =>
|
||||
translateIdent(name) match {
|
||||
case name: IR.Name =>
|
||||
DefinitionArgument.Specified(
|
||||
Name.Literal(name.name, getIdentifiedLocation(name)),
|
||||
name,
|
||||
Some(translateExpression(value)),
|
||||
isSuspended,
|
||||
getIdentifiedLocation(arg)
|
||||
@ -325,6 +338,9 @@ object AstToIR {
|
||||
case _ =>
|
||||
throw new UnhandledEntity(arg, "translateArgumentDefinition")
|
||||
}
|
||||
case _ =>
|
||||
throw new UnhandledEntity(arg, "translateArgumentDefinition")
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates a call-site function argument from its [[AST]] representation
|
||||
@ -406,19 +422,24 @@ object AstToIR {
|
||||
Function.Lambda(realArgs, realBody, getIdentifiedLocation(callable))
|
||||
}
|
||||
case AST.App.Infix(left, fn, right) =>
|
||||
val leftArg = translateCallArgument(left)
|
||||
val rightArg = translateCallArgument(right)
|
||||
|
||||
if (leftArg.name.isDefined) {
|
||||
IR.Error.Syntax(left, IR.Error.Syntax.NamedArgInOperator)
|
||||
} else if (rightArg.name.isDefined) {
|
||||
IR.Error.Syntax(right, IR.Error.Syntax.NamedArgInOperator)
|
||||
} else {
|
||||
Application.Operator.Binary(
|
||||
translateExpression(left),
|
||||
leftArg,
|
||||
Name.Literal(fn.name, getIdentifiedLocation(fn)),
|
||||
translateExpression(right),
|
||||
rightArg,
|
||||
getIdentifiedLocation(callable)
|
||||
)
|
||||
}
|
||||
case AST.App.Prefix(_, _) =>
|
||||
throw new UnhandledEntity(callable, "translateCallable")
|
||||
case AST.App.Section.any(_) =>
|
||||
Error.Syntax(
|
||||
callable,
|
||||
Error.Syntax.UnsupportedSyntax("operator sections")
|
||||
)
|
||||
case AST.App.Section.any(sec) => translateOperatorSection(sec)
|
||||
case AST.Mixfix(nameSegments, args) =>
|
||||
val realNameSegments = nameSegments.collect {
|
||||
case AST.Ident.Var.any(v) => v.name
|
||||
@ -431,13 +452,55 @@ object AstToIR {
|
||||
Application.Prefix(
|
||||
translateExpression(functionName),
|
||||
args.map(translateCallArgument).toList,
|
||||
false,
|
||||
hasDefaultsSuspended = false,
|
||||
getIdentifiedLocation(callable)
|
||||
)
|
||||
case _ => throw new UnhandledEntity(callable, "translateCallable")
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates an operator section from its [[AST]] representation into the
|
||||
* [[IR]] representation.
|
||||
*
|
||||
* @param section the operator section
|
||||
* @return the [[IR]] representation of `section`
|
||||
*/
|
||||
def translateOperatorSection(
|
||||
section: AST.App.Section
|
||||
): Expression = {
|
||||
section match {
|
||||
case AST.App.Section.Left.any(left) =>
|
||||
val leftArg = translateCallArgument(left.arg)
|
||||
|
||||
if (leftArg.name.isDefined) {
|
||||
Error.Syntax(section, Error.Syntax.NamedArgInSection)
|
||||
} else {
|
||||
Application.Operator.Section.Left(
|
||||
leftArg,
|
||||
Name.Literal(left.opr.name, getIdentifiedLocation(left.opr)),
|
||||
getIdentifiedLocation(left)
|
||||
)
|
||||
}
|
||||
case AST.App.Section.Sides.any(sides) =>
|
||||
Application.Operator.Section.Sides(
|
||||
Name.Literal(sides.opr.name, getIdentifiedLocation(sides.opr)),
|
||||
getIdentifiedLocation(sides)
|
||||
)
|
||||
case AST.App.Section.Right.any(right) =>
|
||||
val rightArg = translateCallArgument(right.arg)
|
||||
|
||||
if (rightArg.name.isDefined) {
|
||||
Error.Syntax(section, Error.Syntax.NamedArgInSection)
|
||||
} else {
|
||||
Application.Operator.Section.Right(
|
||||
Name.Literal(right.opr.name, getIdentifiedLocation(right.opr)),
|
||||
translateCallArgument(right.arg),
|
||||
getIdentifiedLocation(right)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates an arbitrary program identifier from its [[AST]] representation
|
||||
* into [[Core]].
|
||||
*
|
||||
@ -457,10 +520,7 @@ object AstToIR {
|
||||
case AST.Ident.Cons(name) =>
|
||||
Name.Literal(name, getIdentifiedLocation(identifier))
|
||||
case AST.Ident.Blank(_) =>
|
||||
Error.Syntax(
|
||||
identifier,
|
||||
Error.Syntax.UnsupportedSyntax("blanks")
|
||||
)
|
||||
Name.Blank(getIdentifiedLocation(identifier))
|
||||
case AST.Ident.Opr.any(_) =>
|
||||
Error.Syntax(
|
||||
identifier,
|
||||
@ -489,15 +549,13 @@ object AstToIR {
|
||||
name: AST,
|
||||
expr: AST
|
||||
): Expression.Binding = {
|
||||
name match {
|
||||
case v @ AST.Ident.Var(name) =>
|
||||
Expression.Binding(
|
||||
Name.Literal(name, getIdentifiedLocation(v)),
|
||||
translateExpression(expr),
|
||||
location
|
||||
)
|
||||
val irName = translateExpression(name)
|
||||
|
||||
irName match {
|
||||
case n: IR.Name =>
|
||||
Expression.Binding(n, translateExpression(expr), location)
|
||||
case _ =>
|
||||
throw new UnhandledEntity(name, "translateAssignment")
|
||||
throw new UnhandledEntity(name, "translateBinding")
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.enso.compiler.codegen
|
||||
|
||||
import org.enso.data
|
||||
import org.enso.data.List1
|
||||
import org.enso.syntax.text.AST
|
||||
import org.enso.syntax.text.AST.Ident.{Opr, Var}
|
||||
|
||||
/** This object contains view patterns that allow matching on the parser [[AST]]
|
||||
* for more sophisticated constructs.
|
||||
@ -43,7 +45,7 @@ object AstView {
|
||||
* @param ast the structure to try and match on
|
||||
* @return the name to which the block is assigned, and the block itself
|
||||
*/
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST.Block)] = {
|
||||
def unapply(ast: AST): Option[(AST.Ident, AST.Block)] = {
|
||||
ast match {
|
||||
case Assignment(name, AST.Block.any(block)) =>
|
||||
Some((name, block))
|
||||
@ -53,7 +55,7 @@ object AstView {
|
||||
}
|
||||
|
||||
object Binding {
|
||||
val bindingOpSym = AST.Ident.Opr("=")
|
||||
val bindingOpSym: Opr = AST.Ident.Opr("=")
|
||||
|
||||
/** Matches an arbitrary binding in the program source.
|
||||
*
|
||||
@ -82,7 +84,7 @@ object AstView {
|
||||
}
|
||||
|
||||
object Assignment {
|
||||
val assignmentOpSym = AST.Ident.Opr("=")
|
||||
val assignmentOpSym: Opr = AST.Ident.Opr("=")
|
||||
|
||||
/** Matches an assignment.
|
||||
*
|
||||
@ -92,16 +94,34 @@ object AstView {
|
||||
* @param ast the structure to try and match on
|
||||
* @return the variable name assigned to, and the expression being assigned
|
||||
*/
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] = {
|
||||
def unapply(ast: AST): Option[(AST.Ident, AST)] = {
|
||||
ast match {
|
||||
case Binding(AST.Ident.Var.any(left), right) => Some((left, right))
|
||||
case Binding(MaybeBlankName(left), right) => Some((left, right))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MaybeBlankName {
|
||||
val blankSym: String = "_"
|
||||
|
||||
/** Matches an identifier that may be a blank `_`.
|
||||
*
|
||||
* @param ast the structure to try and match on
|
||||
* @return the identifier
|
||||
*/
|
||||
def unapply(ast: AST): Option[AST.Ident] = {
|
||||
ast match {
|
||||
case AST.Ident.Var.any(variable) => Some(variable)
|
||||
case AST.Ident.Cons.any(cons) => Some(cons)
|
||||
case AST.Ident.Blank.any(blank) => Some(blank)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Lambda {
|
||||
val lambdaOpSym = AST.Ident.Opr("->")
|
||||
val lambdaOpSym: Opr = AST.Ident.Opr("->")
|
||||
|
||||
/** Matches a lambda expression in the program source.
|
||||
*
|
||||
@ -255,7 +275,7 @@ object AstView {
|
||||
* @param ast the structure to try and match on
|
||||
* @return the variable name and the expression being bound to it
|
||||
*/
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] =
|
||||
def unapply(ast: AST): Option[(AST.Ident, AST)] =
|
||||
MaybeParensed.unapply(ast).flatMap(Assignment.unapply)
|
||||
}
|
||||
|
||||
@ -268,7 +288,7 @@ object AstView {
|
||||
* @return the name of the argument being declared and the expression of
|
||||
* the default value being bound to it
|
||||
*/
|
||||
def unapply(ast: AST): Option[(AST.Ident.Var, AST)] = {
|
||||
def unapply(ast: AST): Option[(AST.Ident, AST)] = {
|
||||
ast match {
|
||||
case MaybeParensed(
|
||||
Binding(
|
||||
@ -290,8 +310,8 @@ object AstView {
|
||||
* @param ast the structure to try and match on
|
||||
* @return the name of the argument
|
||||
*/
|
||||
def unapply(ast: AST): Option[AST.Ident.Var] = ast match {
|
||||
case MaybeParensed(AST.Ident.Var.any(ast)) => Some(ast)
|
||||
def unapply(ast: AST): Option[AST.Ident] = ast match {
|
||||
case MaybeParensed(MaybeBlankName(ast)) => Some(ast)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
@ -521,7 +541,8 @@ object AstView {
|
||||
}
|
||||
|
||||
object CaseExpression {
|
||||
val caseName = data.List1(AST.Ident.Var("case"), AST.Ident.Var("of"))
|
||||
val caseName: List1[Var] =
|
||||
data.List1(AST.Ident.Var("case"), AST.Ident.Var("of"))
|
||||
|
||||
/** Matches on a case expression.
|
||||
*
|
||||
|
@ -9,10 +9,10 @@ import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph => AliasGraph}
|
||||
import org.enso.compiler.pass.analyse.{
|
||||
AliasAnalysis,
|
||||
ApplicationSaturation,
|
||||
DataflowAnalysis,
|
||||
TailCall
|
||||
}
|
||||
import org.enso.compiler.pass.optimise.ApplicationSaturation
|
||||
import org.enso.interpreter.node.callable.argument.ReadArgumentNode
|
||||
import org.enso.interpreter.node.callable.function.{
|
||||
BlockNode,
|
||||
@ -557,6 +557,10 @@ class IRToTruffle(
|
||||
processName(
|
||||
IR.Name.Literal(Constants.Names.THIS_ARGUMENT, location, passData)
|
||||
)
|
||||
case _: IR.Name.Blank =>
|
||||
throw new CompilerError(
|
||||
"Blanks should not be present at codegen time."
|
||||
)
|
||||
}
|
||||
|
||||
setLocation(nameExpr, name.location)
|
||||
@ -593,6 +597,14 @@ class IRToTruffle(
|
||||
context.getBuiltins
|
||||
.compileError()
|
||||
.newInstance(err.message)
|
||||
case err: Error.Redefined.Method =>
|
||||
context.getBuiltins
|
||||
.compileError()
|
||||
.newInstance(err.message)
|
||||
case err: Error.Redefined.Atom =>
|
||||
context.getBuiltins
|
||||
.compileError()
|
||||
.newInstance(err.message)
|
||||
}
|
||||
setLocation(ErrorNode.build(payload), error.location)
|
||||
}
|
||||
@ -740,6 +752,11 @@ class IRToTruffle(
|
||||
throw new CompilerError(
|
||||
s"Explicit operators not supported during codegen but $op found"
|
||||
)
|
||||
case sec: IR.Application.Operator.Section =>
|
||||
throw new CompilerError(
|
||||
s"Explicit operator sections not supported during codegen but " +
|
||||
s"$sec found"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +171,7 @@ object IR {
|
||||
|IR.Empty(
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -251,6 +252,7 @@ object IR {
|
||||
|bindings = $bindings,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -310,6 +312,7 @@ object IR {
|
||||
|name = $name,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -378,6 +381,7 @@ object IR {
|
||||
|arguments = $arguments,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -457,6 +461,7 @@ object IR {
|
||||
|body = $body,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -559,6 +564,7 @@ object IR {
|
||||
|location = $location,
|
||||
|suspended = $suspended,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -567,6 +573,9 @@ object IR {
|
||||
}
|
||||
|
||||
/** A binding expression of the form `name = expr`
|
||||
*
|
||||
* To create a binding that binds no available name, set the name of the
|
||||
* binding to an [[IR.Name.Blank]] (e.g. _ = foo a b).
|
||||
*
|
||||
* @param name the name being bound to
|
||||
* @param expression the expression being bound to `name`
|
||||
@ -618,6 +627,7 @@ object IR {
|
||||
|expression = $expression,
|
||||
|location = $location
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -677,6 +687,7 @@ object IR {
|
||||
|value = $value,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -728,6 +739,7 @@ object IR {
|
||||
|text = $text,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -746,6 +758,48 @@ object IR {
|
||||
}
|
||||
object Name {
|
||||
|
||||
/** Represents occurrences of blank (`_`) expressions.
|
||||
*
|
||||
* @param location the soure 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 Blank(
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Name
|
||||
with IRKind.Sugar {
|
||||
override val name: String = "_"
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
def copy(
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Blank = {
|
||||
val res = Blank(location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Blank =
|
||||
this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Expression.Blank(
|
||||
|location = $location,
|
||||
|passData = $passData,
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
/** The representation of a literal name.
|
||||
*
|
||||
* @param name the literal text of the name
|
||||
@ -790,6 +844,7 @@ object IR {
|
||||
|name = $name,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -837,6 +892,7 @@ object IR {
|
||||
|IR.Name.This(
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -884,6 +940,7 @@ object IR {
|
||||
s"""IR.Name.Here(
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -956,9 +1013,10 @@ object IR {
|
||||
|signature = $signature,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(typed, signature)
|
||||
}
|
||||
@ -1018,6 +1076,7 @@ object IR {
|
||||
|context = $context,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1097,6 +1156,7 @@ object IR {
|
||||
|value = $value,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1162,6 +1222,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1224,6 +1285,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1286,6 +1348,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1348,6 +1411,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1412,6 +1476,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1476,6 +1541,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|""".toSingleLine
|
||||
|
||||
@ -1576,6 +1642,7 @@ object IR {
|
||||
|location = $location,
|
||||
|canBeTCO = $canBeTCO,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1589,8 +1656,16 @@ object IR {
|
||||
|
||||
/** Definition-site arguments in Enso. */
|
||||
sealed trait DefinitionArgument extends IR {
|
||||
|
||||
/** The name of the argument. */
|
||||
val name: IR.Name
|
||||
|
||||
/** The default value of the argument. */
|
||||
val defaultValue: Option[Expression]
|
||||
|
||||
/** Whether or not the argument is suspended. */
|
||||
val suspended: Boolean
|
||||
|
||||
override def mapExpressions(
|
||||
fn: Expression => Expression
|
||||
): DefinitionArgument
|
||||
@ -1600,6 +1675,9 @@ object IR {
|
||||
/** The representation of an argument from a [[Function]] or
|
||||
* [[IR.Module.Scope.Definition.Atom]] definition site.
|
||||
*
|
||||
* To create an ignored argument, the argument name should be an
|
||||
* [[IR.Name.Blank]].
|
||||
*
|
||||
* @param name the name of the argument
|
||||
* @param defaultValue the default value of the argument, if present
|
||||
* @param suspended whether or not the argument has its execution suspended
|
||||
@ -1608,9 +1686,9 @@ object IR {
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Specified(
|
||||
name: IR.Name,
|
||||
override val name: IR.Name,
|
||||
override val defaultValue: Option[Expression],
|
||||
suspended: Boolean,
|
||||
override val suspended: Boolean,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
@ -1665,15 +1743,13 @@ object IR {
|
||||
|suspended = $suspended,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = name :: defaultValue.toList
|
||||
|
||||
}
|
||||
|
||||
// TODO [AA] Add support for `_` ignored arguments.
|
||||
}
|
||||
|
||||
// === Applications =========================================================
|
||||
@ -1751,6 +1827,7 @@ object IR {
|
||||
|hasDefaultsSuspended = $hasDefaultsSuspended,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1806,6 +1883,7 @@ object IR {
|
||||
|target = $target,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1830,9 +1908,9 @@ object IR {
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Binary(
|
||||
left: Expression,
|
||||
left: CallArgument,
|
||||
operator: IR.Name,
|
||||
right: Expression,
|
||||
right: CallArgument,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
@ -1852,9 +1930,9 @@ object IR {
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
left: Expression = left,
|
||||
left: CallArgument = left,
|
||||
operator: IR.Name = operator,
|
||||
right: Expression = right,
|
||||
right: CallArgument = right,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
@ -1867,7 +1945,7 @@ object IR {
|
||||
}
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Binary = {
|
||||
copy(left = fn(left), right = fn(right))
|
||||
copy(left = left.mapExpressions(fn), right = right.mapExpressions(fn))
|
||||
}
|
||||
|
||||
override def toString: String =
|
||||
@ -1878,6 +1956,7 @@ object IR {
|
||||
|right = $right,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -1885,9 +1964,193 @@ object IR {
|
||||
override def children: List[IR] = List(left, operator, right)
|
||||
|
||||
}
|
||||
|
||||
/** Operator sections. */
|
||||
sealed trait Section extends Operator {
|
||||
override def mapExpressions(fn: Expression => Expression): Section
|
||||
}
|
||||
object Section {
|
||||
|
||||
/** Represents a left operator section of the form `(arg op)`.
|
||||
*
|
||||
* @param arg the argument (on the left of the operator)
|
||||
* @param operator the operator
|
||||
* @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 Left(
|
||||
arg: CallArgument,
|
||||
operator: IR.Name,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Section
|
||||
with IRKind.Sugar {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param arg the argument (on the left of the operator)
|
||||
* @param operator the operator
|
||||
* @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(
|
||||
arg: CallArgument = arg,
|
||||
operator: IR.Name = operator,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: IR.Identifier = id
|
||||
): Left = {
|
||||
val res = Left(arg, operator, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
// TODO [AA] Add support for left, right, and centre sections
|
||||
override def mapExpressions(fn: Expression => Expression): Section =
|
||||
copy(
|
||||
arg = arg.mapExpressions(fn),
|
||||
operator = operator.mapExpressions(fn)
|
||||
)
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Application.Operator.Section.Left(
|
||||
|arg = $arg,
|
||||
|operator = $operator,
|
||||
|location = $location,
|
||||
|passData = $passData,
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(arg, operator)
|
||||
}
|
||||
|
||||
/** Represents a sides operator section of the form `(op)`
|
||||
*
|
||||
* @param operator the operator
|
||||
* @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 Sides(
|
||||
operator: IR.Name,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Section
|
||||
with IRKind.Sugar {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param operator the operator
|
||||
* @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(
|
||||
operator: IR.Name = operator,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Sides = {
|
||||
val res = Sides(operator, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Section =
|
||||
copy(operator = operator.mapExpressions(fn))
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Application.Operator.Section.Centre(
|
||||
|operator = $operator,
|
||||
|location = $location,
|
||||
|passData = $passData,
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(operator)
|
||||
}
|
||||
|
||||
/** Represents a right operator section of the form `(op arg)`
|
||||
*
|
||||
* @param operator the operator
|
||||
* @param arg the argument (on the right of the operator)
|
||||
* @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 Right(
|
||||
operator: IR.Name,
|
||||
arg: CallArgument,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Section
|
||||
with IRKind.Sugar {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param operator the operator
|
||||
* @param arg the argument (on the right of the operator)
|
||||
* @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(
|
||||
operator: IR.Name = operator,
|
||||
arg: CallArgument = arg,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Right = {
|
||||
val res = Right(operator, arg, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Section = {
|
||||
copy(
|
||||
operator = operator.mapExpressions(fn),
|
||||
arg = arg.mapExpressions(fn)
|
||||
)
|
||||
}
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Application.Operator.Section.Right(
|
||||
|operator = $operator,
|
||||
|arg = $arg,
|
||||
|location = $location,
|
||||
|passData = $passData,
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = List(operator, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Call-Site Arguments ==================================================
|
||||
@ -1898,6 +2161,9 @@ object IR {
|
||||
/** The name of the argument, if present. */
|
||||
val name: Option[IR.Name]
|
||||
|
||||
/** The expression of the argument, if present. */
|
||||
val value: Expression
|
||||
|
||||
/** Whether or not the argument should be suspended at code generation time.
|
||||
*
|
||||
* A value of `Some(true)` implies that code generation should suspend the
|
||||
@ -1912,6 +2178,9 @@ object IR {
|
||||
object CallArgument {
|
||||
|
||||
/** A representation of an argument at a function call site.
|
||||
*
|
||||
* A [[CallArgument]] where the `value` is an [[IR.Name.Blank]] is a
|
||||
* representation of a lambda shorthand argument.
|
||||
*
|
||||
* @param name the name of the argument being called, if present
|
||||
* @param value the expression being passed as the argument's value
|
||||
@ -1923,7 +2192,7 @@ object IR {
|
||||
*/
|
||||
sealed case class Specified(
|
||||
override val name: Option[IR.Name],
|
||||
value: Expression,
|
||||
override val value: Expression,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val shouldBeSuspended: Option[Boolean] = None,
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
@ -1977,16 +2246,13 @@ object IR {
|
||||
|location = $location,
|
||||
|shouldBeSuspended = $shouldBeSuspended,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
|
||||
override def children: List[IR] = name.toList :+ value
|
||||
|
||||
}
|
||||
|
||||
// TODO [AA] Add support for the `_` lambda shorthand argument (can be
|
||||
// called by name)
|
||||
}
|
||||
|
||||
// === Case Expression ======================================================
|
||||
@ -2059,6 +2325,7 @@ object IR {
|
||||
|fallback = $fallback,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2120,6 +2387,7 @@ object IR {
|
||||
|expression = $expression,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2202,6 +2470,7 @@ object IR {
|
||||
|doc = $doc,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2270,6 +2539,7 @@ object IR {
|
||||
|code = $code,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2316,6 +2586,38 @@ object IR {
|
||||
sealed trait Warning extends Diagnostic
|
||||
object Warning {
|
||||
|
||||
/** Warnings about unused language entities. */
|
||||
sealed trait Unused extends Warning {
|
||||
val name: IR.Name
|
||||
}
|
||||
object Unused {
|
||||
|
||||
/** A warning about an unused function argument.
|
||||
*
|
||||
* @param name the name that is unused
|
||||
*/
|
||||
sealed case class FunctionArgument(override val name: Name)
|
||||
extends Unused {
|
||||
override def message: String = s"Unused function argument ${name.name}."
|
||||
|
||||
override def toString: String = s"Unused.FunctionArgument(${name.name})"
|
||||
|
||||
override val location: Option[IdentifiedLocation] = name.location
|
||||
}
|
||||
|
||||
/** A warning about an unused binding.
|
||||
*
|
||||
* @param name the name that is unused
|
||||
*/
|
||||
sealed case class Binding(override val name: Name) extends Unused {
|
||||
override def message: String = s"Unused variable ${name.name}."
|
||||
|
||||
override def toString: String = s"Unused.Binding(${name.name})"
|
||||
|
||||
override val location: Option[IdentifiedLocation] = name.location
|
||||
}
|
||||
}
|
||||
|
||||
/** Warnings about shadowing names. */
|
||||
sealed trait Shadowed extends Warning {
|
||||
|
||||
@ -2337,7 +2639,7 @@ object IR {
|
||||
) extends Shadowed {
|
||||
|
||||
override def message: String =
|
||||
s"The function parameter $shadowedName is being shadowed by $shadower"
|
||||
s"The argument $shadowedName is shadowed by $shadower."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2399,6 +2701,7 @@ object IR {
|
||||
|ast = $ast,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2446,6 +2749,13 @@ object IR {
|
||||
override def explanation: String = "Unclosed text literal."
|
||||
}
|
||||
|
||||
case object NamedArgInSection extends Reason {
|
||||
override def explanation: String = "Named argument in operator section."
|
||||
}
|
||||
|
||||
case object NamedArgInOperator extends Reason {
|
||||
override def explanation: String = "Named argument in operator section."
|
||||
}
|
||||
}
|
||||
|
||||
/** A representation of an invalid piece of IR.
|
||||
@ -2493,6 +2803,7 @@ object IR {
|
||||
|ir = $ir,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".toSingleLine
|
||||
@ -2510,6 +2821,138 @@ object IR {
|
||||
sealed trait Redefined extends Error
|
||||
object Redefined {
|
||||
|
||||
/** An error representing the redefinition of a method in a given module.
|
||||
* This is also known as a method overload.
|
||||
*
|
||||
* @param atomName the name of the atom the method was being redefined on
|
||||
* @param methodName the method name being redefined on `atomName`
|
||||
* @param location the location in the source to which this error
|
||||
* corresponds
|
||||
* @param passData the pass metadata for the error
|
||||
* @param diagnostics any diagnostics associated with this error.
|
||||
*/
|
||||
sealed case class Method(
|
||||
atomName: IR.Name,
|
||||
methodName: IR.Name,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Redefined
|
||||
with Diagnostic.Kind.Interactive
|
||||
with Module.Scope.Definition
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param atomName the name of the atom the method was being redefined on
|
||||
* @param methodName the method name being redefined on `atomName`
|
||||
* @param location the location in the source to which this error
|
||||
* corresponds
|
||||
* @param passData the pass metadata for the error
|
||||
* @param diagnostics any diagnostics associated with this error.
|
||||
* @param id the identifier for the node
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
atomName: IR.Name = atomName,
|
||||
methodName: IR.Name = methodName,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Method = {
|
||||
val res =
|
||||
Method(atomName, methodName, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def message: String =
|
||||
s"Method overloads are not supported: ${atomName.name}." +
|
||||
s"${methodName.name} is defined multiple times in this module."
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Method = this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Error.Redefined.Method(
|
||||
|atomName = $atomName,
|
||||
|methodName = $methodName,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|
||||
override def children: List[IR] = List(atomName, methodName)
|
||||
}
|
||||
|
||||
/** An error representing the redefinition of an atom in a given module.
|
||||
*
|
||||
* @param atomName the name of the atom being redefined
|
||||
* @param location the location in the source to which this error
|
||||
* corresponds
|
||||
* @param passData the pass metadata for the error
|
||||
* @param diagnostics any diagnostics associated with this error.
|
||||
*/
|
||||
sealed case class Atom(
|
||||
atomName: IR.Name,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Redefined
|
||||
with Diagnostic.Kind.Interactive
|
||||
with Module.Scope.Definition
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
/** Creates a copy of `this`.
|
||||
*
|
||||
* @param atomName the name of the atom the method was being redefined
|
||||
* on
|
||||
* @param location the location in the source to which this error
|
||||
* corresponds
|
||||
* @param passData the pass metadata for the error
|
||||
* @param diagnostics any diagnostics associated with this error.
|
||||
* @param id the identifier for the node
|
||||
* @return a copy of `this`, updated with the specified values
|
||||
*/
|
||||
def copy(
|
||||
atomName: IR.Name = atomName,
|
||||
location: Option[IdentifiedLocation] = location,
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Atom = {
|
||||
val res =
|
||||
Atom(atomName, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def message: String =
|
||||
s"Redefining atoms is not supported: ${atomName.name} is " +
|
||||
s"defined multiple times in this module."
|
||||
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Atom = this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Error.Redefined.Atom(
|
||||
|atomName = $atomName,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
|
||||
override def children: List[IR] = List(atomName)
|
||||
}
|
||||
|
||||
/** An error representing the redefinition of a binding in a given scope.
|
||||
*
|
||||
* While bindings in child scopes are allowed to _shadow_ bindings in
|
||||
@ -2524,7 +2967,7 @@ object IR {
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Redefined
|
||||
with Diagnostic.Kind.Static
|
||||
with Diagnostic.Kind.Interactive
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
@ -2559,6 +3002,7 @@ object IR {
|
||||
|invalidBinding = $invalidBinding,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
|diagnostics = $diagnostics,
|
||||
|id = $id
|
||||
|)
|
||||
|""".stripMargin
|
||||
@ -2772,6 +3216,25 @@ object IR {
|
||||
// === Useful Extension Methods =============================================
|
||||
// ==========================================================================
|
||||
|
||||
/** Adds extension methods for working directly with the diagnostics on the
|
||||
* IR.
|
||||
*
|
||||
* @param ir the IR to add the methods to
|
||||
* @tparam T the concrete type of the IR
|
||||
*/
|
||||
implicit class AsDiagnostics[T <: IR](ir: T) {
|
||||
|
||||
/** Adds a new diagnostic entity to [[IR]].
|
||||
*
|
||||
* @param diagnostic the diagnostic to add
|
||||
* @return [[ir]] with added diagnostics
|
||||
*/
|
||||
def addDiagnostic(diagnostic: IR.Diagnostic): T = {
|
||||
ir.diagnostics.add(diagnostic)
|
||||
ir
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds extension methods for working directly with the metadata on the IR.
|
||||
*
|
||||
* @param ir the IR to add the methods to
|
||||
|
@ -15,7 +15,9 @@ import scala.reflect.ClassTag
|
||||
* Passes that depend on the metadata of other passes should pull this metadata
|
||||
* directly from the IR, and not depend on metadata available in the context.
|
||||
*
|
||||
* Every pass should be implemented as a `case object`.
|
||||
* Every pass should be implemented as a `case object` and should document in
|
||||
* its header the requirements it has for pass configuration and for passes
|
||||
* that must run before it.
|
||||
*/
|
||||
trait IRPass {
|
||||
|
||||
|
@ -35,6 +35,21 @@ import scala.reflect.ClassTag
|
||||
* the lambda.
|
||||
* - A method whose body is a lambda containing a block as its body allocates
|
||||
* no additional scope for the lambda or the block.
|
||||
*
|
||||
* Alias analysis requires its configuration to be in the configuration object.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[org.enso.compiler.pass.PassConfiguration]] containing an instance of
|
||||
* [[AliasAnalysis.Configuration]].
|
||||
* - A [[org.enso.interpreter.runtime.scope.LocalScope]], where relevant.
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]],
|
||||
*/
|
||||
case object AliasAnalysis extends IRPass {
|
||||
|
||||
@ -138,6 +153,7 @@ case object AliasAnalysis extends IRPass {
|
||||
analyseArgumentDefs(args, topLevelGraph, topLevelGraph.rootScope)
|
||||
)
|
||||
.updateMetadata(this -->> Info.Scope.Root(topLevelGraph))
|
||||
case err: IR.Error.Redefined => err
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,7 +345,10 @@ case object AliasAnalysis extends IRPass {
|
||||
throw new CompilerError(
|
||||
"Binary operator occurred during Alias Analysis."
|
||||
)
|
||||
|
||||
case _: IR.Application.Operator.Section =>
|
||||
throw new CompilerError(
|
||||
"Operator section occurred during Alias Analysis."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,18 @@ import scala.collection.mutable
|
||||
* Dataflow analysis is the processes of determining the dependencies between
|
||||
* program expressions.
|
||||
*
|
||||
* This pass needs to be run after [[AliasAnalysis]], [[DemandAnalysis]], and
|
||||
* [[TailCall]]. It also assumes that all members of [[IR.IRKind.Primitive]]
|
||||
* have been removed from the IR by the time it runs. This means that it _must_
|
||||
* run after all desugaring passes.
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[org.enso.interpreter.runtime.scope.LocalScope]], where relevant.
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[AliasAnalysis]]
|
||||
* - [[DemandAnalysis]]
|
||||
* - [[TailCall]]
|
||||
*
|
||||
* It also requires that all members of [[IR.IRKind.Primitive]] have been
|
||||
* removed from the IR by the time it runs.
|
||||
*/
|
||||
//noinspection DuplicatedCode
|
||||
case object DataflowAnalysis extends IRPass {
|
||||
@ -92,6 +100,7 @@ case object DataflowAnalysis extends IRPass {
|
||||
body = analyseExpression(body, info)
|
||||
)
|
||||
.updateMetadata(this -->> info)
|
||||
case err: IR.Error.Redefined => err
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,6 +337,13 @@ case object DataflowAnalysis extends IRPass {
|
||||
"Name occurrence with missing aliasing information."
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Occurrence]
|
||||
|
||||
name match {
|
||||
case _: IR.Name.Blank =>
|
||||
throw new CompilerError(
|
||||
"Blanks should not be present during dataflow analysis."
|
||||
)
|
||||
case _ =>
|
||||
val defIdForName = aliasInfo.graph.defLinkFor(aliasInfo.id)
|
||||
val key = defIdForName match {
|
||||
case Some(defLink) =>
|
||||
@ -344,6 +360,7 @@ case object DataflowAnalysis extends IRPass {
|
||||
|
||||
name.updateMetadata(this -->> info)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs dependency analysis on a case expression.
|
||||
*
|
||||
|
@ -10,9 +10,23 @@ import org.enso.compiler.pass.IRPass
|
||||
* Demand analysis is the process of determining _when_ a suspended term needs
|
||||
* to be forced (where the suspended value is _demanded_).
|
||||
*
|
||||
* This pass needs to be run after [[AliasAnalysis]], and also assumes that
|
||||
* all members of [[IR.IRKind.Primitive]] have been removed from the IR by the
|
||||
* time that it runs.
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
* - [[org.enso.compiler.pass.resolve.IgnoredBindings]]
|
||||
* - [[AliasAnalysis]]
|
||||
* - [[org.enso.compiler.pass.optimise.LambdaConsolidate]]
|
||||
* - [[org.enso.compiler.pass.resolve.OverloadsResolution]]
|
||||
*
|
||||
* Additionally, all members of [[IR.IRKind.Primitive]] must have been removed
|
||||
* from the IR by the time it runs.
|
||||
*/
|
||||
case object DemandAnalysis extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
@ -158,6 +172,10 @@ case object DemandAnalysis extends IRPass {
|
||||
case lit: IR.Name.Literal => lit.copy(location = newNameLocation)
|
||||
case ths: IR.Name.This => ths.copy(location = newNameLocation)
|
||||
case here: IR.Name.Here => here.copy(location = newNameLocation)
|
||||
case _: IR.Name.Blank =>
|
||||
throw new CompilerError(
|
||||
"Blanks should not be present by the time demand analysis runs."
|
||||
)
|
||||
}
|
||||
|
||||
IR.Application.Force(newName, forceLocation)
|
||||
|
@ -7,6 +7,14 @@ import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** A pass that traverses the given root IR and accumulates all the encountered
|
||||
* diagnostic nodes in the root.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - None
|
||||
*/
|
||||
case object GatherDiagnostics extends IRPass {
|
||||
|
||||
|
@ -11,6 +11,17 @@ import org.enso.compiler.pass.IRPass
|
||||
* It is responsible for marking every single expression with whether it is in
|
||||
* tail position or not. This allows the code generator to correctly create the
|
||||
* Truffle nodes.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - The tail position of its expression, where relevant.
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
*/
|
||||
case object TailCall extends IRPass {
|
||||
|
||||
@ -77,6 +88,7 @@ case object TailCall extends IRPass {
|
||||
arguments = args.map(analyseDefArgument)
|
||||
)
|
||||
.updateMetadata(this -->> TailPosition.Tail)
|
||||
case err: IR.Error.Redefined => err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,14 @@ import org.enso.compiler.pass.IRPass
|
||||
*
|
||||
* - The body is a function (lambda)
|
||||
* - The body has `this` at the start of its argument list.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - None
|
||||
*/
|
||||
case object GenerateMethodBodies extends IRPass {
|
||||
|
||||
|
@ -0,0 +1,343 @@
|
||||
package org.enso.compiler.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** This pass translates `_` arguments at application sites to lambda functions.
|
||||
*
|
||||
* This pass has no configuration.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[FreshNameSupply]]
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[GenerateMethodBodies]]
|
||||
* - [[SectionsToBinOp]]
|
||||
* - [[OperatorToFunction]]
|
||||
*/
|
||||
case object LambdaShorthandToLambda extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** Desugars underscore arguments to lambdas for a module.
|
||||
*
|
||||
* @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 = ir.transformExpressions {
|
||||
case x =>
|
||||
x.mapExpressions(
|
||||
runExpression(
|
||||
_,
|
||||
InlineContext(freshNameSupply = moduleContext.freshNameSupply)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Desugars underscore arguments to lambdas for an arbitrary expression.
|
||||
*
|
||||
* @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 = {
|
||||
val freshNameSupply = inlineContext.freshNameSupply.getOrElse(
|
||||
throw new CompilerError(
|
||||
"Desugaring underscore arguments to lambdas requires a fresh name " +
|
||||
"supply."
|
||||
)
|
||||
)
|
||||
|
||||
desugarExpression(ir, freshNameSupply)
|
||||
}
|
||||
|
||||
// === Pass Internals =======================================================
|
||||
|
||||
/** Performs lambda shorthand desugaring on an arbitrary expression.
|
||||
*
|
||||
* @param ir the expression to desugar
|
||||
* @param freshNameSupply the compiler's fresh name supply
|
||||
* @return `ir`, with any lambda shorthand arguments desugared
|
||||
*/
|
||||
def desugarExpression(
|
||||
ir: IR.Expression,
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.Expression = {
|
||||
ir.transformExpressions {
|
||||
case app: IR.Application => desugarApplication(app, freshNameSupply)
|
||||
case caseExpr: IR.Case.Expr => desugarCaseExpr(caseExpr, freshNameSupply)
|
||||
case name: IR.Name => desugarName(name, freshNameSupply)
|
||||
}
|
||||
}
|
||||
|
||||
/** Desugars an arbitrary name occurrence, turning isolated occurrences of
|
||||
* `_` into the `id` function.
|
||||
*
|
||||
* @param name the name to desugar
|
||||
* @param supply the compiler's fresh name supply
|
||||
* @return `name`, desugared where necessary
|
||||
*/
|
||||
def desugarName(name: IR.Name, supply: FreshNameSupply): IR.Expression = {
|
||||
name match {
|
||||
case blank: IR.Name.Blank =>
|
||||
val newName = supply.newName()
|
||||
|
||||
IR.Function.Lambda(
|
||||
List(
|
||||
IR.DefinitionArgument.Specified(
|
||||
IR.Name.Literal(newName.name, None),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
),
|
||||
newName,
|
||||
blank.location
|
||||
)
|
||||
case _ => name
|
||||
}
|
||||
}
|
||||
|
||||
/** Desugars lambda shorthand arguments to an arbitrary function application.
|
||||
*
|
||||
* @param application the function application to desugar
|
||||
* @param freshNameSupply the compiler's supply of fresh names
|
||||
* @return `application`, with any lambda shorthand arguments desugared
|
||||
*/
|
||||
def desugarApplication(
|
||||
application: IR.Application,
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.Expression = {
|
||||
application match {
|
||||
case p @ IR.Application.Prefix(fn, args, _, _, _, _) =>
|
||||
// Determine which arguments are lambda shorthand
|
||||
val argIsUnderscore = determineLambdaShorthand(args)
|
||||
|
||||
// Generate a new name for the arg value for each shorthand arg
|
||||
val updatedArgs =
|
||||
args
|
||||
.zip(argIsUnderscore)
|
||||
.map(updateShorthandArg(_, freshNameSupply))
|
||||
.map {
|
||||
case s @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
s.copy(value = desugarExpression(value, freshNameSupply))
|
||||
}
|
||||
|
||||
// Generate a definition arg instance for each shorthand arg
|
||||
val defArgs = updatedArgs.zip(argIsUnderscore).map {
|
||||
case (arg, isShorthand) => generateDefinitionArg(arg, isShorthand)
|
||||
}
|
||||
val actualDefArgs = defArgs.collect {
|
||||
case Some(defArg) => defArg
|
||||
}
|
||||
|
||||
// Determine whether or not the function itself is shorthand
|
||||
val functionIsShorthand = fn.isInstanceOf[IR.Name.Blank]
|
||||
val (updatedFn, updatedName) = if (functionIsShorthand) {
|
||||
val newFn = freshNameSupply
|
||||
.newName()
|
||||
.copy(
|
||||
location = fn.location,
|
||||
passData = fn.passData,
|
||||
diagnostics = fn.diagnostics
|
||||
)
|
||||
val newName = newFn.name
|
||||
(newFn, Some(newName))
|
||||
} else (fn, None)
|
||||
|
||||
val processedApp = p.copy(
|
||||
function = updatedFn,
|
||||
arguments = updatedArgs
|
||||
)
|
||||
|
||||
// Wrap the app in lambdas from right to left, lambda / shorthand arg
|
||||
val appResult =
|
||||
actualDefArgs.foldRight(processedApp: IR.Expression)((arg, body) =>
|
||||
IR.Function.Lambda(List(arg), body, None)
|
||||
)
|
||||
|
||||
// If the function is shorthand, do the same
|
||||
if (functionIsShorthand) {
|
||||
IR.Function.Lambda(
|
||||
List(
|
||||
IR.DefinitionArgument.Specified(
|
||||
IR.Name.Literal(updatedName.get, fn.location),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
),
|
||||
appResult,
|
||||
None
|
||||
)
|
||||
} else appResult
|
||||
case f @ IR.Application.Force(tgt, _, _, _) =>
|
||||
f.copy(target = desugarExpression(tgt, freshNameSupply))
|
||||
case _: IR.Application.Operator =>
|
||||
throw new CompilerError(
|
||||
"Operators should be desugared by the point of underscore " +
|
||||
"to lambda conversion."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines, positionally, which of the application arguments are lambda
|
||||
* shorthand arguments.
|
||||
*
|
||||
* @param args the application arguments
|
||||
* @return a list containing `true` for a given position if the arg in that
|
||||
* position is lambda shorthand, otherwise `false`
|
||||
*/
|
||||
def determineLambdaShorthand(args: List[IR.CallArgument]): List[Boolean] = {
|
||||
args.map {
|
||||
case IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
value match {
|
||||
case _: IR.Name.Blank => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a new name to replace a shorthand argument, as well as the
|
||||
* corresponding definition argument.
|
||||
*
|
||||
* @param argAndIsShorthand the arguments, and whether or not the argument in
|
||||
* the corresponding position is shorthand
|
||||
* @return the above described pair for a given position if the argument in
|
||||
* a given position is shorthand, otherwise [[None]].
|
||||
*/
|
||||
def updateShorthandArg(
|
||||
argAndIsShorthand: (IR.CallArgument, Boolean),
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.CallArgument = {
|
||||
val arg = argAndIsShorthand._1
|
||||
val isShorthand = argAndIsShorthand._2
|
||||
|
||||
arg match {
|
||||
case s @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
if (isShorthand) {
|
||||
val newName = freshNameSupply
|
||||
.newName()
|
||||
.copy(
|
||||
location = value.location,
|
||||
passData = value.passData,
|
||||
diagnostics = value.diagnostics
|
||||
)
|
||||
|
||||
s.copy(value = newName)
|
||||
} else s
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a corresponding definition argument to a call argument that was
|
||||
* previously lambda shorthand.
|
||||
*
|
||||
* @param arg the argument to generate a corresponding def argument to
|
||||
* @param isShorthand whether or not `arg` was shorthand
|
||||
* @return a corresponding definition argument if `arg` `isShorthand`,
|
||||
* otherwise [[None]]
|
||||
*/
|
||||
def generateDefinitionArg(
|
||||
arg: IR.CallArgument,
|
||||
isShorthand: Boolean
|
||||
): Option[IR.DefinitionArgument] = {
|
||||
if (isShorthand) {
|
||||
arg match {
|
||||
case IR.CallArgument.Specified(_, value, _, _, _, _) =>
|
||||
// Note [Safe Casting to IR.Name.Literal]
|
||||
val defArgName =
|
||||
IR.Name.Literal(value.asInstanceOf[IR.Name.Literal].name, None)
|
||||
|
||||
Some(
|
||||
IR.DefinitionArgument.Specified(
|
||||
defArgName,
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
)
|
||||
}
|
||||
} else None
|
||||
}
|
||||
|
||||
/* Note [Safe Casting to IR.Name.Literal]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* This cast is entirely safe here as, by construction in
|
||||
* `updateShorthandArg`, any arg for which `isShorthand` is true has its
|
||||
* value as an `IR.Name.Literal`.
|
||||
*/
|
||||
|
||||
/** Performs desugaring of lambda shorthand arguments in a case expression.
|
||||
*
|
||||
* In the case where a user writes `case _ of`, this gets converted into a
|
||||
* lambda (`x -> case x of`).
|
||||
*
|
||||
* @param caseExpr the case expression to desugar
|
||||
* @param freshNameSupply the compiler's supply of fresh names
|
||||
* @return `caseExpr`, with any lambda shorthand desugared
|
||||
*/
|
||||
def desugarCaseExpr(
|
||||
caseExpr: IR.Case.Expr,
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.Expression = {
|
||||
val newBranches = caseExpr.branches.map(
|
||||
_.mapExpressions(expr => desugarExpression(expr, freshNameSupply))
|
||||
)
|
||||
val newFallback =
|
||||
caseExpr.fallback.map(desugarExpression(_, freshNameSupply))
|
||||
|
||||
caseExpr.scrutinee match {
|
||||
case IR.Name.Blank(loc, passData, diagnostics) =>
|
||||
val scrutineeName =
|
||||
freshNameSupply
|
||||
.newName()
|
||||
.copy(
|
||||
location = loc,
|
||||
passData = passData,
|
||||
diagnostics = diagnostics
|
||||
)
|
||||
|
||||
val lambdaArg = IR.DefinitionArgument.Specified(
|
||||
scrutineeName.copy(id = IR.randomId),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
|
||||
val newCaseExpr = caseExpr.copy(
|
||||
scrutinee = scrutineeName,
|
||||
branches = newBranches,
|
||||
fallback = newFallback
|
||||
)
|
||||
|
||||
IR.Function.Lambda(
|
||||
List(lambdaArg),
|
||||
newCaseExpr,
|
||||
caseExpr.location,
|
||||
passData = caseExpr.passData,
|
||||
diagnostics = caseExpr.diagnostics
|
||||
)
|
||||
case x =>
|
||||
caseExpr.copy(
|
||||
scrutinee = desugarExpression(x, freshNameSupply),
|
||||
branches = newBranches,
|
||||
fallback = newFallback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package org.enso.compiler.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** This pass lifts any special operators (ones reserved by the language
|
||||
* implementation) into their own special IR constructs.
|
||||
*/
|
||||
case object LiftSpecialOperators extends IRPass {
|
||||
|
||||
/** A desugaring pass does not output any data. */
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
override def runModule(
|
||||
ir: IR.Module,
|
||||
moduleContext: ModuleContext
|
||||
): IR.Module =
|
||||
ir.transformExpressions({
|
||||
case x => runExpression(x, new InlineContext)
|
||||
})
|
||||
|
||||
/** Executes the lifting pass in an inline context.
|
||||
*
|
||||
* @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.transformExpressions({
|
||||
case IR.Application.Operator.Binary(l, op, r, loc, meta, _) =>
|
||||
op.name match {
|
||||
case IR.Type.Ascription.name =>
|
||||
IR.Type.Ascription(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Subsumption.name =>
|
||||
IR.Type.Set
|
||||
.Subsumption(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Equality.name =>
|
||||
IR.Type.Set
|
||||
.Equality(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Concat.name =>
|
||||
IR.Type.Set
|
||||
.Concat(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Union.name =>
|
||||
IR.Type.Set
|
||||
.Union(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Intersection.name =>
|
||||
IR.Type.Set
|
||||
.Intersection(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case IR.Type.Set.Subtraction.name =>
|
||||
IR.Type.Set
|
||||
.Subtraction(
|
||||
runExpression(l, inlineContext),
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
case _ =>
|
||||
IR.Application.Operator
|
||||
.Binary(
|
||||
runExpression(l, inlineContext),
|
||||
op,
|
||||
runExpression(r, inlineContext),
|
||||
loc,
|
||||
meta
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
@ -4,7 +4,16 @@ import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** This pass converts usages of operators to calls to standard functions. */
|
||||
/** This pass converts usages of operators to calls to standard functions.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
* - [[GenerateMethodBodies]]
|
||||
* - [[SectionsToBinOp]]
|
||||
*/
|
||||
case object OperatorToFunction extends IRPass {
|
||||
|
||||
/** A purely desugaring pass has no analysis output. */
|
||||
@ -45,10 +54,8 @@ case object OperatorToFunction extends IRPass {
|
||||
IR.Application.Prefix(
|
||||
op,
|
||||
List(
|
||||
IR.CallArgument
|
||||
.Specified(None, runExpression(l, inlineContext), l.location),
|
||||
IR.CallArgument
|
||||
.Specified(None, runExpression(r, inlineContext), r.location)
|
||||
l.mapExpressions(runExpression(_, inlineContext)),
|
||||
r.mapExpressions(runExpression(_, inlineContext))
|
||||
),
|
||||
hasDefaultsSuspended = false,
|
||||
loc,
|
||||
|
@ -0,0 +1,208 @@
|
||||
package org.enso.compiler.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.Application.Operator.Section
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** This pass converts operator sections to applications of binary operators.
|
||||
*
|
||||
* This pass has no configuration.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[FreshNameSupply]].
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
* - [[GenerateMethodBodies]]
|
||||
*/
|
||||
case object SectionsToBinOp extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** Performs section to binary operator conversion on an IR module.
|
||||
*
|
||||
* @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 = ir.transformExpressions {
|
||||
case x =>
|
||||
runExpression(
|
||||
x,
|
||||
new InlineContext(freshNameSupply = moduleContext.freshNameSupply)
|
||||
)
|
||||
}
|
||||
|
||||
/** Performs section to binary operator conversion on an IR expression.
|
||||
*
|
||||
* @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 = {
|
||||
val freshNameSupply = inlineContext.freshNameSupply.getOrElse(
|
||||
throw new CompilerError(
|
||||
"A fresh name supply is required for sections desugaring."
|
||||
)
|
||||
)
|
||||
|
||||
ir.transformExpressions {
|
||||
case sec: IR.Application.Operator.Section =>
|
||||
desugarSections(sec, freshNameSupply)
|
||||
}
|
||||
}
|
||||
|
||||
/** Desugars operator sections to fully-saturated applications of operators.
|
||||
*
|
||||
* For a left sections it will generate a partially-applied function. For
|
||||
* right sections it will generate a lambda. For sides sections it is forced
|
||||
* to generate a lambda returning a partially applied function as we do not
|
||||
* currently support partial application without the this argument.
|
||||
*
|
||||
* @param section the section to desugar
|
||||
* @return the result of desugaring `section`
|
||||
*/
|
||||
//noinspection DuplicatedCode
|
||||
def desugarSections(
|
||||
section: IR.Application.Operator.Section,
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.Expression = {
|
||||
section match {
|
||||
case Section.Left(arg, op, loc, passData, diagnostics) =>
|
||||
IR.Application.Prefix(
|
||||
op,
|
||||
List(arg),
|
||||
hasDefaultsSuspended = false,
|
||||
loc,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
case Section.Sides(op, loc, passData, diagnostics) =>
|
||||
val leftArgName = freshNameSupply.newName()
|
||||
val leftCallArg =
|
||||
IR.CallArgument.Specified(None, leftArgName, None, None)
|
||||
val leftDefArg = IR.DefinitionArgument.Specified(
|
||||
// Ensure it has a different identifier
|
||||
leftArgName.copy(id = IR.randomId),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
|
||||
val opCall = IR.Application.Prefix(
|
||||
op,
|
||||
List(leftCallArg),
|
||||
hasDefaultsSuspended = false,
|
||||
loc,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
|
||||
IR.Function.Lambda(
|
||||
List(leftDefArg),
|
||||
opCall,
|
||||
loc,
|
||||
canBeTCO = true,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
|
||||
/* Note [Blanks in Right Sections]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* While the naiive compositional translation of `(- _)` first translates
|
||||
* the section into a function applying `-` to two arguments, one of which
|
||||
* is a blank, the compositional nature of the blanks translation actually
|
||||
* works against us here.
|
||||
*
|
||||
* As the `LambdaShorthandToLambda` pass can only operate on the
|
||||
* application with the blanks, it can't know to push the blank outside
|
||||
* that application chain. To that end, we have to handle this case
|
||||
* specially here instead. What we want it to translate to is as follows:
|
||||
*
|
||||
* `(- _)` == `x -> (- x)` == `x -> y -> y - x`
|
||||
*
|
||||
* We implement this special case here.
|
||||
*/
|
||||
|
||||
case Section.Right(op, arg, loc, passData, diagnostics) =>
|
||||
val leftArgName = freshNameSupply.newName()
|
||||
val leftCallArg =
|
||||
IR.CallArgument.Specified(None, leftArgName, None, None)
|
||||
val leftDefArg =
|
||||
IR.DefinitionArgument.Specified(
|
||||
// Ensure it has a different identifier
|
||||
leftArgName.copy(id = IR.randomId),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
|
||||
if (arg.value.isInstanceOf[IR.Name.Blank]) {
|
||||
// Note [Blanks in Right Sections]
|
||||
val rightArgName = freshNameSupply.newName()
|
||||
val rightCallArg =
|
||||
IR.CallArgument.Specified(None, rightArgName, None, None)
|
||||
val rightDefArg = IR.DefinitionArgument.Specified(
|
||||
rightArgName.copy(id = IR.randomId),
|
||||
None,
|
||||
suspended = false,
|
||||
None
|
||||
)
|
||||
|
||||
val opCall = IR.Application.Prefix(
|
||||
op,
|
||||
List(leftCallArg, rightCallArg),
|
||||
hasDefaultsSuspended = false,
|
||||
loc,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
|
||||
val leftLam = IR.Function.Lambda(
|
||||
List(leftDefArg),
|
||||
opCall,
|
||||
None
|
||||
)
|
||||
|
||||
IR.Function.Lambda(
|
||||
List(rightDefArg),
|
||||
leftLam,
|
||||
loc,
|
||||
canBeTCO = true,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
} else {
|
||||
val opCall = IR.Application.Prefix(
|
||||
op,
|
||||
List(leftCallArg, arg),
|
||||
hasDefaultsSuspended = false,
|
||||
loc,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
|
||||
IR.Function.Lambda(
|
||||
List(leftDefArg),
|
||||
opCall,
|
||||
loc,
|
||||
canBeTCO = true,
|
||||
passData,
|
||||
diagnostics
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
package org.enso.compiler.pass.lint
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
/** This pass performs linting for unused names, generating warnings if it finds
|
||||
* any.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
* - [[IgnoredBindings]]
|
||||
* - [[org.enso.compiler.pass.optimise.LambdaConsolidate]]
|
||||
*/
|
||||
case object UnusedBindings extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** Lints a module.
|
||||
*
|
||||
* @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 = ir.transformExpressions {
|
||||
case x => x.mapExpressions(runExpression(_, InlineContext()))
|
||||
}
|
||||
|
||||
/** Lints an arbitrary expression.
|
||||
*
|
||||
* @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.transformExpressions {
|
||||
case binding: IR.Expression.Binding => lintBinding(binding, inlineContext)
|
||||
case function: IR.Function => lintFunction(function, inlineContext)
|
||||
}
|
||||
|
||||
// === Pass Internals =======================================================
|
||||
|
||||
/** Lints a binding.
|
||||
*
|
||||
* @param binding the binding to lint
|
||||
* @param context the inline context in which linting is taking place
|
||||
* @return `binding`, with any lints attached
|
||||
*/
|
||||
def lintBinding(
|
||||
binding: IR.Expression.Binding,
|
||||
context: InlineContext
|
||||
): IR.Expression.Binding = {
|
||||
val isIgnored = binding
|
||||
.unsafeGetMetadata(
|
||||
IgnoredBindings,
|
||||
"Binding ignore information is required for linting."
|
||||
)
|
||||
.isIgnored
|
||||
|
||||
val aliasInfo = binding
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
"Aliasing information is required for linting."
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Occurrence]
|
||||
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
|
||||
|
||||
if (!isIgnored && !isUsed) {
|
||||
binding.copy(
|
||||
expression = runExpression(binding.expression, context)
|
||||
)
|
||||
binding.addDiagnostic(IR.Warning.Unused.Binding(binding.name))
|
||||
} else {
|
||||
binding.copy(
|
||||
expression = runExpression(binding.expression, context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Lints a function.
|
||||
*
|
||||
* @param function the function to lint
|
||||
* @param context the inline context in which linting is taking place
|
||||
* @return `function`, with any lints attached
|
||||
*/
|
||||
def lintFunction(
|
||||
function: IR.Function,
|
||||
@unused context: InlineContext
|
||||
): IR.Function = {
|
||||
function match {
|
||||
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
|
||||
lam.copy(
|
||||
arguments = args.map(lintFunctionArgument(_, context)),
|
||||
body = runExpression(body, context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs linting on a function argument.
|
||||
*
|
||||
* @param argument the function argument to lint
|
||||
* @param context the inline context in which linting is taking place
|
||||
* @return `argument`, with any lints attached
|
||||
*/
|
||||
def lintFunctionArgument(
|
||||
argument: IR.DefinitionArgument,
|
||||
context: InlineContext
|
||||
): IR.DefinitionArgument = {
|
||||
val isIgnored = argument
|
||||
.unsafeGetMetadata(
|
||||
IgnoredBindings,
|
||||
"Argument ignore information is required for linting."
|
||||
)
|
||||
.isIgnored
|
||||
|
||||
val aliasInfo = argument
|
||||
.unsafeGetMetadata(
|
||||
AliasAnalysis,
|
||||
"Aliasing information missing but is required for linting."
|
||||
)
|
||||
.unsafeAs[AliasAnalysis.Info.Occurrence]
|
||||
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
|
||||
|
||||
argument match {
|
||||
case s @ IR.DefinitionArgument.Specified(name, default, _, _, _, _) =>
|
||||
if (!isIgnored && !isUsed) {
|
||||
s.copy(
|
||||
defaultValue = default.map(runExpression(_, context))
|
||||
)
|
||||
.addDiagnostic(IR.Warning.Unused.FunctionArgument(name))
|
||||
} else s
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,31 @@
|
||||
package org.enso.compiler.pass.analyse
|
||||
package org.enso.compiler.pass.optimise
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.ir.MetadataStorage._
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.IRPass
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgument
|
||||
|
||||
/** This optimisation pass recognises fully-saturated applications of known
|
||||
* functions and writes analysis data that allows optimisation of them to
|
||||
* specific nodes at codegen time.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[org.enso.compiler.pass.PassConfiguration]] containing an instance of
|
||||
* [[ApplicationSaturation.Configuration]].
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
* - [[org.enso.compiler.pass.resolve.IgnoredBindings]]
|
||||
* - [[LambdaConsolidate]]
|
||||
*/
|
||||
case object ApplicationSaturation extends IRPass {
|
||||
|
@ -29,12 +29,22 @@ import org.enso.syntax.text.Location
|
||||
* x y z -> ...
|
||||
* }}}
|
||||
*
|
||||
* It requires [[org.enso.compiler.pass.analyse.AliasAnalysis]] to be run
|
||||
* _directly_ before it.
|
||||
*
|
||||
* Please note that this pass invalidates _all_ metdata on the transformed
|
||||
* portions of the program, and hence must be run before the deeper analysis
|
||||
* passes.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[FreshNameSupply]].
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
|
||||
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
* - [[org.enso.compiler.pass.resolve.IgnoredBindings]]
|
||||
* - [[AliasAnalysis]], which must be run _directly_ before this pass.
|
||||
*/
|
||||
case object LambdaConsolidate extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
@ -56,8 +66,7 @@ case object LambdaConsolidate extends IRPass {
|
||||
runExpression(
|
||||
x,
|
||||
new InlineContext(
|
||||
freshNameSupply = moduleContext.freshNameSupply,
|
||||
passConfiguration = moduleContext.passConfiguration
|
||||
freshNameSupply = moduleContext.freshNameSupply
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -280,6 +289,7 @@ case object LambdaConsolidate extends IRPass {
|
||||
)
|
||||
case ths: IR.Name.This => ths
|
||||
case here: IR.Name.Here => here
|
||||
case blank: IR.Name.Blank => blank
|
||||
}
|
||||
} else {
|
||||
name
|
||||
|
@ -0,0 +1,235 @@
|
||||
package org.enso.compiler.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.ir.MetadataStorage._
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
/** This pass translates ignored bindings (of the form `_`) into fresh names
|
||||
* internally, as well as marks all bindings as whether or not they were
|
||||
* ignored.
|
||||
*
|
||||
* This pass has no configuration.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - A [[FreshNameSupply]].
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
* - [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]]
|
||||
*/
|
||||
case object IgnoredBindings extends IRPass {
|
||||
override type Metadata = State
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** Desugars ignored bindings for a module.
|
||||
*
|
||||
* @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 = ir.transformExpressions {
|
||||
case x =>
|
||||
x.mapExpressions(
|
||||
runExpression(
|
||||
_,
|
||||
InlineContext(freshNameSupply = moduleContext.freshNameSupply)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Desugars ignored bindings for an arbitrary expression.
|
||||
*
|
||||
* @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 = {
|
||||
val freshNameSupply = inlineContext.freshNameSupply.getOrElse(
|
||||
throw new CompilerError(
|
||||
"Desugaring underscore arguments to lambdas requires a fresh name " +
|
||||
"supply."
|
||||
)
|
||||
)
|
||||
|
||||
desugarExpression(ir, freshNameSupply)
|
||||
}
|
||||
|
||||
// === Pass Internals =======================================================
|
||||
|
||||
/** Desugars ignored bindings of the form `_` in an arbitrary expression.
|
||||
*
|
||||
* @param expression the expression to perform desugaring on
|
||||
* @param supply the compiler's fresh name supply
|
||||
* @return `expression`, with any ignored bidings desugared
|
||||
*/
|
||||
private def desugarExpression(
|
||||
expression: IR.Expression,
|
||||
supply: FreshNameSupply
|
||||
): IR.Expression = {
|
||||
expression.transformExpressions {
|
||||
case binding: IR.Expression.Binding => desugarBinding(binding, supply)
|
||||
case function: IR.Function => desugarFunction(function, supply)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs desugaring of ignored bindings for a binding.
|
||||
*
|
||||
* @param binding the binding to desugar
|
||||
* @param supply the compiler's supply of fresh names
|
||||
* @return `binding`, with any ignored bindings desugared
|
||||
*/
|
||||
def desugarBinding(
|
||||
binding: IR.Expression.Binding,
|
||||
supply: FreshNameSupply
|
||||
): IR.Expression.Binding = {
|
||||
if (isIgnore(binding.name)) {
|
||||
val newName = supply
|
||||
.newName()
|
||||
.copy(
|
||||
location = binding.name.location,
|
||||
passData = binding.name.passData,
|
||||
diagnostics = binding.name.diagnostics
|
||||
)
|
||||
|
||||
binding
|
||||
.copy(
|
||||
name = newName,
|
||||
expression = desugarExpression(binding.expression, supply)
|
||||
)
|
||||
.updateMetadata(this -->> State.Ignored)
|
||||
} else {
|
||||
binding
|
||||
.copy(
|
||||
expression = desugarExpression(binding.expression, supply)
|
||||
)
|
||||
.updateMetadata(this -->> State.NotIgnored)
|
||||
}
|
||||
}
|
||||
|
||||
/** Performs desugaring of ignored function arguments.
|
||||
*
|
||||
* @param function the function to perform desugaring on
|
||||
* @param supply the compiler's fresh name supply
|
||||
* @return `function`, with any ignores desugared
|
||||
*/
|
||||
def desugarFunction(
|
||||
function: IR.Function,
|
||||
supply: FreshNameSupply
|
||||
): IR.Function = {
|
||||
function match {
|
||||
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
|
||||
val argIsIgnore = args.map(isIgnoreArg)
|
||||
val newArgs = args.zip(argIsIgnore).map {
|
||||
case (arg, isIgnore) => genNewArg(arg, isIgnore, supply)
|
||||
}
|
||||
|
||||
lam.copy(
|
||||
arguments = newArgs,
|
||||
body = desugarExpression(body, supply)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a new argument name for `arg` if `isIgnored` is true.
|
||||
*
|
||||
* It also handles recursing through the default values.
|
||||
*
|
||||
* @param arg the argument definition to process
|
||||
* @param isIgnored whether or not `arg` is ignored
|
||||
* @param freshNameSupply the compiler's fresh name supply
|
||||
* @return `arg`, if `isIgnored` is `false`, otherwise `arg` with a new name
|
||||
*/
|
||||
def genNewArg(
|
||||
arg: IR.DefinitionArgument,
|
||||
isIgnored: Boolean,
|
||||
freshNameSupply: FreshNameSupply
|
||||
): IR.DefinitionArgument = {
|
||||
arg match {
|
||||
case spec: IR.DefinitionArgument.Specified =>
|
||||
if (isIgnored) {
|
||||
val newName = freshNameSupply
|
||||
.newName()
|
||||
.copy(
|
||||
location = arg.name.location,
|
||||
passData = arg.name.passData,
|
||||
diagnostics = arg.name.diagnostics
|
||||
)
|
||||
|
||||
spec
|
||||
.copy(
|
||||
name = newName,
|
||||
defaultValue =
|
||||
spec.defaultValue.map(desugarExpression(_, freshNameSupply))
|
||||
)
|
||||
.updateMetadata(this -->> State.Ignored)
|
||||
} else {
|
||||
spec
|
||||
.copy(
|
||||
defaultValue =
|
||||
spec.defaultValue.map(desugarExpression(_, freshNameSupply))
|
||||
)
|
||||
.updateMetadata(this -->> State.NotIgnored)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if a given function definition argument is an ignore.
|
||||
*
|
||||
* @param ir the definition argument to check
|
||||
* @return `true` if `ir` represents an ignore, otherwise `false`
|
||||
*/
|
||||
def isIgnoreArg(ir: IR.DefinitionArgument): Boolean = {
|
||||
ir match {
|
||||
case IR.DefinitionArgument.Specified(name, _, _, _, _, _) =>
|
||||
isIgnore(name)
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if a given name represents an ignored argument.
|
||||
*
|
||||
* @param ir the name to check
|
||||
* @return `true` if `ir` represents an ignored argument, otherwise `false`
|
||||
*/
|
||||
def isIgnore(ir: IR.Name): Boolean = {
|
||||
ir match {
|
||||
case _: IR.Name.Blank => true
|
||||
case IR.Name.Literal(name, _, _, _) => name == "_"
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
// === Pass Metadata ========================================================
|
||||
|
||||
/** States whether or not the binding was ignored. */
|
||||
sealed trait State extends IRPass.Metadata {
|
||||
val isIgnored: Boolean
|
||||
}
|
||||
object State {
|
||||
|
||||
/** States that the binding is ignored. */
|
||||
case object Ignored extends State {
|
||||
override val metadataName: String = "IgnoredBindings.State.Ignored"
|
||||
override val isIgnored: Boolean = true
|
||||
}
|
||||
|
||||
/** States that the binding is not ignored. */
|
||||
case object NotIgnored extends State {
|
||||
override val metadataName: String = "IgnoredBindings.State.NotIgnored"
|
||||
override val isIgnored: Boolean = false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package org.enso.compiler.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.IRPass
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
/** This pass performs static detection of method overloads and emits errors
|
||||
* at the overload definition site if they are detected. It also checks for
|
||||
* overloads of atom contructors using the same rule.
|
||||
*
|
||||
* Method resolution proceeds in the following order:
|
||||
*
|
||||
* 1. Methods defined directly on the atom are resolved first.
|
||||
* 2. Extension methods in the current scope are resolved next.
|
||||
* 3. Extension methods in imported scopes are resolved last.
|
||||
*
|
||||
* This means that it is possible to shadow an imported extension method with
|
||||
* one defined locally, and this does not count as an overload.
|
||||
*
|
||||
* This pass requires the context to provide:
|
||||
*
|
||||
* - Nothing
|
||||
*
|
||||
* It must have the following passes run before it:
|
||||
*
|
||||
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
|
||||
*/
|
||||
case object OverloadsResolution extends IRPass {
|
||||
override type Metadata = IRPass.Metadata.Empty
|
||||
override type Config = IRPass.Configuration.Default
|
||||
|
||||
/** Performs static detection of method overloads within a given module.
|
||||
*
|
||||
* @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,
|
||||
@unused moduleContext: ModuleContext
|
||||
): IR.Module = {
|
||||
var seenAtoms: Set[String] = Set()
|
||||
var seenMethods: Map[String, Set[String]] = Map()
|
||||
|
||||
val atoms = ir.bindings.collect {
|
||||
case atom: IR.Module.Scope.Definition.Atom => atom
|
||||
}
|
||||
|
||||
val newAtoms: List[IR.Module.Scope.Definition] = atoms.map(atom => {
|
||||
if (seenAtoms.contains(atom.name.name)) {
|
||||
IR.Error.Redefined.Atom(atom.name, atom.location)
|
||||
} else {
|
||||
seenAtoms = seenAtoms + atom.name.name
|
||||
atom
|
||||
}
|
||||
})
|
||||
|
||||
val methods = ir.bindings.collect {
|
||||
case meth: IR.Module.Scope.Definition.Method =>
|
||||
seenMethods = seenMethods + (meth.typeName.name -> Set())
|
||||
meth
|
||||
}
|
||||
|
||||
val newMethods: List[IR.Module.Scope.Definition] = methods.map(method => {
|
||||
if (seenMethods(method.typeName.name).contains(method.methodName.name)) {
|
||||
IR.Error.Redefined
|
||||
.Method(method.typeName, method.methodName, method.location)
|
||||
} else {
|
||||
val currentMethods = seenMethods(method.typeName.name)
|
||||
seenMethods =
|
||||
seenMethods + (method.typeName.name ->
|
||||
(currentMethods + method.methodName.name))
|
||||
|
||||
method
|
||||
}
|
||||
})
|
||||
|
||||
ir.copy(
|
||||
bindings = newAtoms ::: newMethods
|
||||
)
|
||||
}
|
||||
|
||||
/** This pass does nothing for the expression flow.
|
||||
*
|
||||
* @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
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package org.enso.compiler.test
|
||||
|
||||
import org.enso.compiler.codegen.AstToIR
|
||||
import org.enso.compiler.codegen.AstToIr
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.PassManager
|
||||
@ -44,7 +44,7 @@ trait CompilerRunner {
|
||||
* @return the [[IR]] representing [[source]]
|
||||
*/
|
||||
def toIrModule: IR.Module = {
|
||||
AstToIR.translate(source.toAST)
|
||||
AstToIr.translate(source.toAST)
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ trait CompilerRunner {
|
||||
* @return the [[IR]] representing [[source]], if it is a valid expression
|
||||
*/
|
||||
def toIrExpression: Option[IR.Expression] = {
|
||||
AstToIR.translateInline(source.toAST)
|
||||
AstToIr.translateInline(source.toAST)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
package org.enso.compiler.test.codegen
|
||||
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.syntax.text.Debug
|
||||
|
||||
class AstToIRTest extends CompilerTest {
|
||||
|
||||
"AST translation of lambda definitions" should {
|
||||
"result in a syntax error when defined with multiple arguments" in {
|
||||
val ir =
|
||||
"""x y -> x + y
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Error.Syntax]
|
||||
|
||||
ir.asInstanceOf[IR.Error.Syntax].message shouldEqual
|
||||
"Syntax is not supported yet: pattern matching function arguments."
|
||||
}
|
||||
|
||||
"support standard lambda chaining" in {
|
||||
val ir =
|
||||
"""
|
||||
|x -> y -> z -> x
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body shouldBe an[IR.Function.Lambda]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package org.enso.compiler.test.codegen
|
||||
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class AstToIrTest extends CompilerTest {
|
||||
|
||||
"AST translation of lambda definitions" should {
|
||||
"result in a syntax error when defined with multiple arguments" in {
|
||||
val ir =
|
||||
"""x y -> x + y
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Error.Syntax]
|
||||
|
||||
ir.asInstanceOf[IR.Error.Syntax].message shouldEqual
|
||||
"Syntax is not supported yet: pattern matching function arguments."
|
||||
}
|
||||
|
||||
"support standard lambda chaining" in {
|
||||
val ir =
|
||||
"""
|
||||
|x -> y -> z -> x
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body shouldBe an[IR.Function.Lambda]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of operators" should {
|
||||
"disallow named arguments to operators" in {
|
||||
val ir =
|
||||
"""
|
||||
|(a = 1) + 10
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Error.Syntax]
|
||||
ir.asInstanceOf[IR.Error.Syntax]
|
||||
.reason shouldBe an[IR.Error.Syntax.NamedArgInOperator.type]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of operator sections" should {
|
||||
"work properly for left sections" in {
|
||||
val ir =
|
||||
"""
|
||||
|(1 +)
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Application.Operator.Section.Left]
|
||||
}
|
||||
|
||||
"work properly for sides sections" in {
|
||||
val ir =
|
||||
"""
|
||||
|(+)
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Application.Operator.Section.Sides]
|
||||
}
|
||||
|
||||
"work properly for right sections" in {
|
||||
val ir =
|
||||
"""
|
||||
|(+ 1)
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Application.Operator.Section.Right]
|
||||
}
|
||||
|
||||
"disallow sections with named arguments" in {
|
||||
val ir =
|
||||
"""
|
||||
|(+ (left=1))
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Error.Syntax]
|
||||
ir.asInstanceOf[IR.Error.Syntax]
|
||||
.reason shouldBe an[IR.Error.Syntax.NamedArgInSection.type]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of function applications" should {
|
||||
"allow use of blank arguments" in {
|
||||
val ir =
|
||||
"""
|
||||
|a b _ d
|
||||
|""".stripMargin.toIrExpression.get
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
ir.arguments(1) shouldBe an[IR.CallArgument.Specified]
|
||||
ir.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
|
||||
"allow use of named blank arguments" in {
|
||||
val ir =
|
||||
"""
|
||||
|a b (f = _) c
|
||||
|""".stripMargin.toIrExpression.get
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
ir.arguments(1) shouldBe an[IR.CallArgument.Specified]
|
||||
ir.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
|
||||
"allow method-call syntax on a blank" in {
|
||||
val ir =
|
||||
"""
|
||||
|_.foo a b
|
||||
|""".stripMargin.toIrExpression.get
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
ir.arguments.head shouldBe an[IR.CallArgument.Specified]
|
||||
ir.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
|
||||
"allow functions in applications to be blanks" in {
|
||||
val ir =
|
||||
"""
|
||||
|_ a b c
|
||||
|""".stripMargin.toIrExpression.get
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
ir.function shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of case expressions" should {
|
||||
"support a blank scrutinee" in {
|
||||
val ir =
|
||||
"""
|
||||
|case _ of
|
||||
| Cons a b -> a + b
|
||||
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Case.Expr]
|
||||
|
||||
ir.scrutinee shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of function definitions" should {
|
||||
"support ignored arguments" in {
|
||||
val ir =
|
||||
"""
|
||||
|_ -> a -> a + 20
|
||||
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
ir.arguments.head shouldBe an[IR.DefinitionArgument.Specified]
|
||||
val blankArg =
|
||||
ir.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
blankArg.name shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
|
||||
"support suspended ignored arguments" in {
|
||||
val ir =
|
||||
"""
|
||||
|~_ -> a -> a + 20
|
||||
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
ir.arguments.head shouldBe an[IR.DefinitionArgument.Specified]
|
||||
val blankArg =
|
||||
ir.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
blankArg.name shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
|
||||
"support ignored arguments with defaults" in {
|
||||
val ir =
|
||||
"""
|
||||
|(_ = 10) -> a -> a + 20
|
||||
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
ir.arguments.head shouldBe an[IR.DefinitionArgument.Specified]
|
||||
val blankArg =
|
||||
ir.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
blankArg.name shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
}
|
||||
|
||||
"AST translation of bindings" should {
|
||||
"allow ignored bindings" in {
|
||||
val ir =
|
||||
"""
|
||||
|_ = foo a b
|
||||
|""".stripMargin.toIrExpression.get
|
||||
|
||||
ir shouldBe an[IR.Expression.Binding]
|
||||
val binding = ir.asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
binding.name shouldBe an[IR.Name.Blank]
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,9 @@ import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Link, Occurrence}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph, Info}
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
LambdaShorthandToLambda,
|
||||
OperatorToFunction,
|
||||
SectionsToBinOp
|
||||
}
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
@ -22,11 +23,12 @@ class AliasAnalysisTest extends CompilerTest {
|
||||
/** The passes that need to be run before the alias analysis pass. */
|
||||
val precursorPasses: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda
|
||||
)
|
||||
|
||||
val passConfig = PassConfiguration(
|
||||
val passConfig: PassConfiguration = PassConfiguration(
|
||||
AliasAnalysis -->> AliasAnalysis.Configuration(true)
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,6 @@ import org.enso.compiler.pass.analyse.{
|
||||
}
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
}
|
||||
import org.enso.compiler.pass.optimise.LambdaConsolidate
|
||||
@ -28,7 +27,6 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
/** The passes that must be run before the dataflow analysis pass. */
|
||||
val precursorPasses: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction,
|
||||
AliasAnalysis,
|
||||
LambdaConsolidate,
|
||||
@ -37,7 +35,7 @@ class DataflowAnalysisTest extends CompilerTest {
|
||||
TailCall
|
||||
)
|
||||
|
||||
val passConfig = PassConfiguration(
|
||||
val passConfig: PassConfiguration = PassConfiguration(
|
||||
AliasAnalysis -->> AliasAnalysis.Configuration()
|
||||
)
|
||||
|
||||
|
@ -6,7 +6,6 @@ import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse.{AliasAnalysis, DemandAnalysis}
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
}
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
@ -20,7 +19,6 @@ class DemandAnalysisTest extends CompilerTest {
|
||||
/** The passes that must be run before the demand analysis pass. */
|
||||
val precursorPasses: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction,
|
||||
AliasAnalysis
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ import org.enso.compiler.pass.analyse.TailCall.TailPosition
|
||||
import org.enso.compiler.pass.analyse.{AliasAnalysis, TailCall}
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
}
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
@ -34,7 +33,6 @@ class TailCallTest extends CompilerTest {
|
||||
|
||||
val precursorPasses: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction,
|
||||
AliasAnalysis
|
||||
)
|
||||
|
@ -0,0 +1,480 @@
|
||||
package org.enso.compiler.test.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LambdaShorthandToLambda,
|
||||
OperatorToFunction,
|
||||
SectionsToBinOp
|
||||
}
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class LambdaShorthandToLambdaTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
val passes: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction
|
||||
)
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(passes, passConfiguration)
|
||||
|
||||
/** Adds an extension method for running desugaring on the input IR.
|
||||
*
|
||||
* @param ir the IR to desugar
|
||||
*/
|
||||
implicit class DesugarExpression(ir: IR.Expression) {
|
||||
|
||||
/** Runs lambda shorthand desugaring on [[ir]].
|
||||
*
|
||||
* @param inlineContext the inline context in which the desugaring takes
|
||||
* place
|
||||
* @return [[ir]], with all lambda shorthand desugared
|
||||
*/
|
||||
def desugar(implicit inlineContext: InlineContext): IR.Expression = {
|
||||
LambdaShorthandToLambda.runExpression(ir, inlineContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes an inline context.
|
||||
*
|
||||
* @return a new inline context
|
||||
*/
|
||||
def mkInlineContext: InlineContext = {
|
||||
InlineContext(freshNameSupply = Some(new FreshNameSupply))
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Desugaring of underscore arguments" should {
|
||||
"Work for simple applications with underscore args" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|foo a _ b _
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnArgName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn.body shouldBe an[IR.Function.Lambda]
|
||||
val irFnNested = irFn.body.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnNestedArgName = irFnNested.arguments.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
.name
|
||||
|
||||
irFnNested.body shouldBe an[IR.Application.Prefix]
|
||||
val body = irFn.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
val arg2Name = body
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
val arg4Name = body
|
||||
.arguments(3)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
irFnArgName.name shouldEqual arg2Name.name
|
||||
irFnNestedArgName.name shouldEqual arg4Name.name
|
||||
}
|
||||
|
||||
"Work for named applications of underscore args" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|foo (a = _) b _
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnArgName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn.body shouldBe an[IR.Function.Lambda]
|
||||
val irFnNested = irFn.body.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnNestedArgName =
|
||||
irFnNested.arguments.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
.name
|
||||
|
||||
irFnNested.body shouldBe an[IR.Application.Prefix]
|
||||
val app = irFnNested.body.asInstanceOf[IR.Application.Prefix]
|
||||
val arg1Name =
|
||||
app.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
val arg3Name =
|
||||
app
|
||||
.arguments(2)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
irFnArgName.name shouldEqual arg1Name.name
|
||||
irFnNestedArgName.name shouldEqual arg3Name.name
|
||||
}
|
||||
|
||||
"Work if the function in an application is an underscore arg" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|_ a b
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnArgName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn.body shouldBe an[IR.Application.Prefix]
|
||||
val app = irFn.body.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
val fnName = app.function.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
irFnArgName.name shouldEqual fnName.name
|
||||
}
|
||||
|
||||
"Work with mixfix functions" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|if _ then a
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val irFnArgName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn.body shouldBe an[IR.Application.Prefix]
|
||||
val app = irFn.body.asInstanceOf[IR.Application.Prefix]
|
||||
val arg1Name = app.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
irFnArgName.name shouldEqual arg1Name.name
|
||||
}
|
||||
|
||||
"Work for an underscore scrutinee in a case expression" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|case _ of
|
||||
| Nil -> 0
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
|
||||
val irLam = ir.asInstanceOf[IR.Function.Lambda]
|
||||
irLam.arguments.length shouldEqual 1
|
||||
|
||||
val lamArgName =
|
||||
irLam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
val lamBody = irLam.body.asInstanceOf[IR.Case.Expr]
|
||||
|
||||
lamBody.scrutinee shouldBe an[IR.Name.Literal]
|
||||
lamBody.scrutinee
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
.name shouldEqual lamArgName.name
|
||||
}
|
||||
|
||||
"work correctly for operators" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(10 + _)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
val argName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
val body = irFn.body.asInstanceOf[IR.Application.Prefix]
|
||||
val rightArg = body.arguments(1).value.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
argName.name shouldEqual rightArg.name
|
||||
}
|
||||
|
||||
"work correctly for left operator sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(_ +)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val argName = irFn.arguments.head.name
|
||||
val body = irFn.body.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
body.arguments.length shouldEqual 1
|
||||
|
||||
val leftArgName = body.arguments.head.value.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
argName.name shouldEqual leftArgName.name
|
||||
}
|
||||
|
||||
"work correctly for centre operator sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(_ + _)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val arg1Name = irFn.arguments.head.name
|
||||
|
||||
val irFn2 = irFn.body.asInstanceOf[IR.Function.Lambda]
|
||||
val arg2Name = irFn2.arguments.head.name
|
||||
|
||||
val app = irFn2.body.asInstanceOf[IR.Application.Prefix]
|
||||
app.arguments.length shouldEqual 2
|
||||
|
||||
val leftArg = app.arguments.head.value.asInstanceOf[IR.Name.Literal]
|
||||
val rightArg = app.arguments(1).value.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
arg1Name.name shouldEqual leftArg.name
|
||||
arg2Name.name shouldEqual rightArg.name
|
||||
}
|
||||
|
||||
"work correctly for right operator sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(- _)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
val rightArgName = irFn.arguments.head.name
|
||||
|
||||
irFn.body shouldBe an[IR.Function.Lambda]
|
||||
val irFn2 = irFn.body.asInstanceOf[IR.Function.Lambda]
|
||||
val leftArgName = irFn2.arguments.head.name
|
||||
|
||||
irFn2.body shouldBe an[IR.Application.Prefix]
|
||||
val app = irFn2.body.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
app.arguments.length shouldEqual 2
|
||||
|
||||
val appLeftName = app.arguments.head.value.asInstanceOf[IR.Name.Literal]
|
||||
val appRightName = app.arguments(1).value.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
leftArgName.name shouldEqual appLeftName.name
|
||||
rightArgName.name shouldEqual appRightName.name
|
||||
}
|
||||
}
|
||||
|
||||
"Nested underscore arguments" should {
|
||||
"work for applications" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|a _ (fn _ c)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda]
|
||||
.body shouldBe an[IR.Application.Prefix]
|
||||
val irBody = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
irBody
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Function.Lambda]
|
||||
val lamArg = irBody
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
val lamArgArgName =
|
||||
lamArg.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
lamArg.body shouldBe an[IR.Application.Prefix]
|
||||
val lamArgBody = lamArg.body.asInstanceOf[IR.Application.Prefix]
|
||||
val lamArgBodyArg1Name = lamArgBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
|
||||
}
|
||||
|
||||
"work in named applications" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|a _ (fn (t = _) c)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
ir.asInstanceOf[IR.Function.Lambda]
|
||||
.body shouldBe an[IR.Application.Prefix]
|
||||
val irBody = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
irBody
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value shouldBe an[IR.Function.Lambda]
|
||||
val lamArg = irBody
|
||||
.arguments(1)
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
val lamArgArgName =
|
||||
lamArg.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
lamArg.body shouldBe an[IR.Application.Prefix]
|
||||
val lamArgBody = lamArg.body.asInstanceOf[IR.Application.Prefix]
|
||||
val lamArgBodyArg1Name = lamArgBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
|
||||
}
|
||||
|
||||
"work in function argument defaults" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|a -> (b = f _ 1) -> f a
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val bArgFn = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
val bArg1 =
|
||||
bArgFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
|
||||
bArg1.defaultValue shouldBe defined
|
||||
bArg1.defaultValue.get shouldBe an[IR.Function.Lambda]
|
||||
val default = bArg1.defaultValue.get.asInstanceOf[IR.Function.Lambda]
|
||||
val defaultArgName = default.arguments.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
.name
|
||||
|
||||
default.body shouldBe an[IR.Application.Prefix]
|
||||
val defBody = default.body.asInstanceOf[IR.Application.Prefix]
|
||||
val defBodyArg1Name = defBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
defaultArgName.name shouldEqual defBodyArg1Name.name
|
||||
}
|
||||
|
||||
"work for case expressions" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|case _ of
|
||||
| Nil -> f _ b
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val nilBranch = ir
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.body
|
||||
.asInstanceOf[IR.Case.Expr]
|
||||
.branches
|
||||
.head
|
||||
.asInstanceOf[IR.Case.Branch]
|
||||
.expression
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
nilBranch.body shouldBe an[IR.Function.Lambda]
|
||||
val nilBody = nilBranch.body.asInstanceOf[IR.Function.Lambda]
|
||||
val nilBodyArgName =
|
||||
nilBody.arguments.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
.name
|
||||
|
||||
nilBody.body shouldBe an[IR.Application.Prefix]
|
||||
val nilBodyBody = nilBody.body.asInstanceOf[IR.Application.Prefix]
|
||||
val nilBodyBodyArg1Name = nilBodyBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
nilBodyArgName.name shouldEqual nilBodyBodyArg1Name.name
|
||||
}
|
||||
}
|
||||
|
||||
"A single lambda shorthand" should {
|
||||
"translate to `id` in general" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|x = _
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Expression.Binding]
|
||||
val expr = ir.asInstanceOf[IR.Expression.Binding].expression
|
||||
|
||||
expr shouldBe an[IR.Function.Lambda]
|
||||
}
|
||||
|
||||
"translate to an `id` in argument defaults" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(x = _) -> x
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
irFn.arguments.head shouldBe an[IR.DefinitionArgument.Specified]
|
||||
val arg =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
|
||||
arg.defaultValue shouldBe defined
|
||||
arg.defaultValue.get shouldBe an[IR.Function.Lambda]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package org.enso.compiler.test.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.core.IR.IdentifiedLocation
|
||||
import org.enso.compiler.pass.desugar.LiftSpecialOperators
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.syntax.text.Location
|
||||
|
||||
class LiftSpecialOperatorsTest extends CompilerTest {
|
||||
|
||||
// === Utilities ============================================================
|
||||
|
||||
val ctx = InlineContext()
|
||||
val modCtx = ModuleContext()
|
||||
|
||||
/** Tests whether a given operator is lifted correctly into the corresponding
|
||||
* special construct.
|
||||
*
|
||||
* @param opInfo the operator symbol
|
||||
* @param constructor the way to construct the operator
|
||||
*/
|
||||
def testOperator(
|
||||
opInfo: IR.Type.Info,
|
||||
constructor: (
|
||||
IR.Expression,
|
||||
IR.Expression,
|
||||
Option[IdentifiedLocation]
|
||||
) => IR.Expression
|
||||
): Unit = s"The ${opInfo.name} operator" should {
|
||||
val op = IR.Name.Literal(opInfo.name, None)
|
||||
val left = IR.Empty(None)
|
||||
val right = IR.Empty(None)
|
||||
val loc = IdentifiedLocation(Location(1, 20))
|
||||
|
||||
val expressionIR = IR.Application.Operator.Binary(
|
||||
left,
|
||||
op,
|
||||
right,
|
||||
Some(loc)
|
||||
)
|
||||
val outputExpressionIR = constructor(
|
||||
left,
|
||||
right,
|
||||
Some(loc)
|
||||
)
|
||||
|
||||
"be lifted by the pass in an inline context" in {
|
||||
LiftSpecialOperators
|
||||
.runExpression(expressionIR, ctx) shouldEqual outputExpressionIR
|
||||
}
|
||||
|
||||
"be lifted by the pass in a module context" in {
|
||||
val moduleInput = expressionIR.asModuleDefs
|
||||
val moduleOutput = outputExpressionIR.asModuleDefs
|
||||
|
||||
LiftSpecialOperators
|
||||
.runModule(moduleInput, modCtx) shouldEqual moduleOutput
|
||||
}
|
||||
|
||||
"work recursively where necessary" in {
|
||||
val recursiveIR =
|
||||
IR.Application.Operator.Binary(expressionIR, op, right, None)
|
||||
val recursiveIROutput = constructor(
|
||||
constructor(left, right, Some(loc)),
|
||||
right,
|
||||
None
|
||||
)
|
||||
|
||||
LiftSpecialOperators
|
||||
.runExpression(recursiveIR, ctx) shouldEqual recursiveIROutput
|
||||
}
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
testOperator(IR.Type.Ascription, IR.Type.Ascription(_, _, _))
|
||||
|
||||
testOperator(
|
||||
IR.Type.Set.Subsumption,
|
||||
IR.Type.Set.Subsumption(_, _, _)
|
||||
)
|
||||
|
||||
testOperator(IR.Type.Set.Equality, IR.Type.Set.Equality(_, _, _))
|
||||
|
||||
testOperator(IR.Type.Set.Concat, IR.Type.Set.Concat(_, _, _))
|
||||
|
||||
testOperator(IR.Type.Set.Union, IR.Type.Set.Union(_, _, _))
|
||||
|
||||
testOperator(IR.Type.Set.Intersection, IR.Type.Set.Intersection(_, _, _))
|
||||
|
||||
testOperator(IR.Type.Set.Subtraction, IR.Type.Set.Subtraction(_, _, _))
|
||||
}
|
@ -28,13 +28,14 @@ class OperatorToFunctionTest extends CompilerTest {
|
||||
): (IR.Application.Operator.Binary, IR.Application.Prefix) = {
|
||||
val loc = IdentifiedLocation(Location(1, 33))
|
||||
|
||||
val binOp = IR.Application.Operator.Binary(left, name, right, Some(loc))
|
||||
val leftArg = IR.CallArgument.Specified(None, left, left.location)
|
||||
val rightArg = IR.CallArgument.Specified(None, right, right.location)
|
||||
|
||||
val binOp =
|
||||
IR.Application.Operator.Binary(leftArg, name, rightArg, Some(loc))
|
||||
val opFn = IR.Application.Prefix(
|
||||
name,
|
||||
List(
|
||||
IR.CallArgument.Specified(None, left, left.location),
|
||||
IR.CallArgument.Specified(None, right, right.location)
|
||||
),
|
||||
List(leftArg, rightArg),
|
||||
hasDefaultsSuspended = false,
|
||||
Some(loc)
|
||||
)
|
||||
@ -48,9 +49,13 @@ class OperatorToFunctionTest extends CompilerTest {
|
||||
val opName = IR.Name.Literal("=:=", None)
|
||||
val left = IR.Empty(None)
|
||||
val right = IR.Empty(None)
|
||||
val rightArg = IR.CallArgument.Specified(None, IR.Empty(None), None)
|
||||
|
||||
val (operator, operatorFn) = genOprAndFn(opName, left, right)
|
||||
|
||||
val oprArg = IR.CallArgument.Specified(None, operator, None)
|
||||
val oprFnArg = IR.CallArgument.Specified(None, operatorFn, None)
|
||||
|
||||
"be translated to functions" in {
|
||||
OperatorToFunction.runExpression(operator, ctx) shouldEqual operatorFn
|
||||
}
|
||||
@ -64,13 +69,10 @@ class OperatorToFunctionTest extends CompilerTest {
|
||||
|
||||
"be translated recursively" in {
|
||||
val recursiveIR =
|
||||
IR.Application.Operator.Binary(operator, opName, right, None)
|
||||
IR.Application.Operator.Binary(oprArg, opName, rightArg, None)
|
||||
val recursiveIRResult = IR.Application.Prefix(
|
||||
opName,
|
||||
List(
|
||||
IR.CallArgument.Specified(None, operatorFn, operatorFn.location),
|
||||
IR.CallArgument.Specified(None, right, right.location)
|
||||
),
|
||||
List(oprFnArg, rightArg),
|
||||
hasDefaultsSuspended = false,
|
||||
None
|
||||
)
|
||||
|
@ -0,0 +1,167 @@
|
||||
package org.enso.compiler.test.pass.desugar
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.pass.desugar.{GenerateMethodBodies, SectionsToBinOp}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class SectionsToBinOpTest extends CompilerTest {
|
||||
|
||||
// === Test Configuration ===================================================
|
||||
|
||||
val passes: List[IRPass] = List(
|
||||
GenerateMethodBodies
|
||||
)
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(passes, passConfiguration)
|
||||
|
||||
/** Adds an extension method for running desugaring on the input IR.
|
||||
*
|
||||
* @param ir the IR to desugar
|
||||
*/
|
||||
implicit class DesugarExpression(ir: IR.Expression) {
|
||||
|
||||
/** Runs section desugaring on [[ir]].
|
||||
*
|
||||
* @param inlineContext the inline context in which the desugaring takes
|
||||
* place
|
||||
* @return [[ir]], with all sections desugared
|
||||
*/
|
||||
def desugar(implicit inlineContext: InlineContext): IR.Expression = {
|
||||
SectionsToBinOp.runExpression(ir, inlineContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes an inline context.
|
||||
*
|
||||
* @return a new inline context
|
||||
*/
|
||||
def mkInlineContext: InlineContext = {
|
||||
InlineContext(freshNameSupply = Some(new FreshNameSupply))
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Operator section desugaring" should {
|
||||
"work for left sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(1 +)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Application.Prefix]
|
||||
ir.asInstanceOf[IR.Application.Prefix].arguments.length shouldEqual 1
|
||||
}
|
||||
|
||||
"work for sides sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(+)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
|
||||
val irLam = ir.asInstanceOf[IR.Function.Lambda]
|
||||
irLam.arguments.length shouldEqual 1
|
||||
|
||||
val lamArgName =
|
||||
irLam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
val lamBody = irLam.body.asInstanceOf[IR.Application.Prefix]
|
||||
lamBody.arguments.length shouldEqual 1
|
||||
val lamBodyFirstArg =
|
||||
lamBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
lamBodyFirstArg.name shouldEqual lamArgName.name
|
||||
lamBodyFirstArg.getId should not equal lamArgName.getId
|
||||
}
|
||||
|
||||
"work for right sections" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(+ 1)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
|
||||
val irLam = ir.asInstanceOf[IR.Function.Lambda]
|
||||
irLam.arguments.length shouldEqual 1
|
||||
|
||||
val lamArgName =
|
||||
irLam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
val lamBody = irLam.body.asInstanceOf[IR.Application.Prefix]
|
||||
lamBody.arguments.length shouldEqual 2
|
||||
val lamBodyFirstArg =
|
||||
lamBody.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
lamBodyFirstArg.name shouldEqual lamArgName.name
|
||||
lamBodyFirstArg.getId should not equal lamArgName.getId
|
||||
}
|
||||
|
||||
"work when the section is nested" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|x -> (x +)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
ir.body shouldBe an[IR.Application.Prefix]
|
||||
ir.body.asInstanceOf[IR.Application.Prefix].arguments.length shouldEqual 1
|
||||
}
|
||||
|
||||
"flip the arguments when a right section's argument is a blank" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|(- _)
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
|
||||
ir shouldBe an[IR.Function.Lambda]
|
||||
val irFn = ir.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
val rightArgName =
|
||||
irFn.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn.body shouldBe an[IR.Function.Lambda]
|
||||
val irFn2 = irFn.body.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
val leftArgName =
|
||||
irFn2.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
|
||||
|
||||
irFn2.body shouldBe an[IR.Application.Prefix]
|
||||
val app = irFn2.body.asInstanceOf[IR.Application.Prefix]
|
||||
|
||||
val appLeftName = app.arguments.head
|
||||
.asInstanceOf[IR.CallArgument.Specified]
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
val appRightName = app
|
||||
.arguments(1)
|
||||
.value
|
||||
.asInstanceOf[IR.Name.Literal]
|
||||
|
||||
leftArgName.name shouldEqual appLeftName.name
|
||||
rightArgName.name shouldEqual appRightName.name
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package org.enso.compiler.test.pass.lint
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse._
|
||||
import org.enso.compiler.pass.desugar.{GenerateMethodBodies, LambdaShorthandToLambda, OperatorToFunction, SectionsToBinOp}
|
||||
import org.enso.compiler.pass.lint.UnusedBindings
|
||||
import org.enso.compiler.pass.optimise.{ApplicationSaturation, LambdaConsolidate}
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.interpreter.runtime.scope.LocalScope
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
class UnusedBindingsTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
val passes: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
SectionsToBinOp,
|
||||
OperatorToFunction,
|
||||
LambdaShorthandToLambda,
|
||||
IgnoredBindings,
|
||||
AliasAnalysis,
|
||||
LambdaConsolidate,
|
||||
AliasAnalysis,
|
||||
DemandAnalysis,
|
||||
ApplicationSaturation,
|
||||
TailCall,
|
||||
DataflowAnalysis
|
||||
)
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration(
|
||||
ApplicationSaturation -->> ApplicationSaturation.Configuration(),
|
||||
AliasAnalysis -->> AliasAnalysis.Configuration()
|
||||
)
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(passes, passConfiguration)
|
||||
|
||||
/** Adds an extension method for running linting on the input IR.
|
||||
*
|
||||
* @param ir the IR to lint
|
||||
*/
|
||||
implicit class LintExpression(ir: IR.Expression) {
|
||||
|
||||
/** Runs unused name linting on [[ir]].
|
||||
*
|
||||
* @param inlineContext the inline context in which the desugaring takes
|
||||
* place
|
||||
* @return [[ir]], with all unused names linted
|
||||
*/
|
||||
def lint(implicit inlineContext: InlineContext): IR.Expression = {
|
||||
UnusedBindings.runExpression(ir, inlineContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes an inline context.
|
||||
*
|
||||
* @return a new inline context
|
||||
*/
|
||||
def mkInlineContext: InlineContext = {
|
||||
InlineContext(
|
||||
freshNameSupply = Some(new FreshNameSupply),
|
||||
localScope = Some(LocalScope.root),
|
||||
isInTailPosition = Some(false)
|
||||
)
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Unused bindings linting" should {
|
||||
"attach a warning to an unused function argument" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|x -> 10
|
||||
|""".stripMargin.preprocessExpression.get.lint
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
val lintMeta = ir.arguments.head.diagnostics.collect {
|
||||
case u: IR.Warning.Unused.FunctionArgument => u
|
||||
}
|
||||
|
||||
lintMeta should not be empty
|
||||
lintMeta.head shouldBe an[IR.Warning.Unused.FunctionArgument]
|
||||
lintMeta.head.name.name shouldEqual "x"
|
||||
}
|
||||
|
||||
"not attach a warning to an unused function argument if it is an ignore" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|_ -> 10
|
||||
|""".stripMargin.preprocessExpression.get.lint
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
|
||||
ir.arguments.head.diagnostics.toList shouldBe empty
|
||||
}
|
||||
|
||||
"attach a warning to an unused binding" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|a = 10
|
||||
|""".stripMargin.preprocessExpression.get.lint
|
||||
.asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
val lintMeta = ir.diagnostics.collect {
|
||||
case u: IR.Warning.Unused.Binding => u
|
||||
}
|
||||
|
||||
lintMeta should not be empty
|
||||
lintMeta.head shouldBe an[IR.Warning.Unused.Binding]
|
||||
lintMeta.head.name.name shouldEqual "a"
|
||||
}
|
||||
|
||||
"not attach a warning to an unused binding if it is an ignore" in {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|_ = 10
|
||||
|""".stripMargin.preprocessExpression.get.lint
|
||||
.asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
ir.diagnostics.toList shouldBe empty
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package org.enso.compiler.test.pass.analyse
|
||||
package org.enso.compiler.test.pass.optimise
|
||||
|
||||
import org.enso.compiler.context.{InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse.ApplicationSaturation.{CallSaturation, FunctionSpec, Metadata}
|
||||
import org.enso.compiler.pass.analyse.{AliasAnalysis, ApplicationSaturation}
|
||||
import org.enso.compiler.pass.desugar.{LiftSpecialOperators, OperatorToFunction}
|
||||
import org.enso.compiler.pass.optimise.ApplicationSaturation.{CallSaturation, FunctionSpec, Metadata}
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.compiler.pass.desugar.OperatorToFunction
|
||||
import org.enso.compiler.pass.optimise.ApplicationSaturation
|
||||
import org.enso.compiler.pass.{PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.interpreter.node.ExpressionNode
|
||||
@ -13,7 +14,6 @@ import org.enso.interpreter.runtime.callable.argument.CallArgument
|
||||
import org.enso.interpreter.runtime.scope.LocalScope
|
||||
|
||||
import scala.annotation.unused
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
class ApplicationSaturationTest extends CompilerTest {
|
||||
|
||||
@ -52,7 +52,6 @@ class ApplicationSaturationTest extends CompilerTest {
|
||||
)
|
||||
|
||||
val passes = List(
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction,
|
||||
AliasAnalysis
|
||||
)
|
@ -6,7 +6,6 @@ import org.enso.compiler.pass.PassConfiguration._
|
||||
import org.enso.compiler.pass.analyse.AliasAnalysis
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction
|
||||
}
|
||||
import org.enso.compiler.pass.optimise.LambdaConsolidate
|
||||
@ -19,7 +18,6 @@ class LambdaConsolidateTest extends CompilerTest {
|
||||
|
||||
val precursorPasses: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LiftSpecialOperators,
|
||||
OperatorToFunction,
|
||||
AliasAnalysis
|
||||
)
|
||||
|
@ -0,0 +1,134 @@
|
||||
package org.enso.compiler.test.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.desugar.{
|
||||
GenerateMethodBodies,
|
||||
LambdaShorthandToLambda
|
||||
}
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings
|
||||
import org.enso.compiler.pass.resolve.IgnoredBindings.State
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
class IgnoredBindingsTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
val passes: List[IRPass] = List(
|
||||
GenerateMethodBodies,
|
||||
LambdaShorthandToLambda
|
||||
)
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(passes, passConfiguration)
|
||||
|
||||
/** Adds an extension method for running desugaring on the input IR.
|
||||
*
|
||||
* @param ir the IR to desugar
|
||||
*/
|
||||
implicit class DesugarExpression(ir: IR.Expression) {
|
||||
|
||||
/** Runs ignores desugaring on [[ir]].
|
||||
*
|
||||
* @param inlineContext the inline context in which the desugaring takes
|
||||
* place
|
||||
* @return [[ir]], with all ignores desugared
|
||||
*/
|
||||
def desugar(implicit inlineContext: InlineContext): IR.Expression = {
|
||||
IgnoredBindings.runExpression(ir, inlineContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes an inline context.
|
||||
*
|
||||
* @return a new inline context
|
||||
*/
|
||||
def mkInlineContext: InlineContext = {
|
||||
InlineContext(freshNameSupply = Some(new FreshNameSupply))
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Ignored bindings desugaring for function args" should {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|_ -> (x = _ -> 1) -> x
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
val blankArg =
|
||||
ir.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
val xArg = ir.body
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.arguments
|
||||
.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
|
||||
"replace ignored arguments with fresh names" in {
|
||||
blankArg.name shouldBe an[IR.Name.Literal]
|
||||
}
|
||||
|
||||
"mark ignored arguments as ignored" in {
|
||||
blankArg.getMetadata(IgnoredBindings) shouldEqual Some(State.Ignored)
|
||||
}
|
||||
|
||||
"mark normal arguments as not ignored" in {
|
||||
xArg.getMetadata(IgnoredBindings) shouldEqual Some(State.NotIgnored)
|
||||
}
|
||||
|
||||
"work when deeply nested" in {
|
||||
val nestedIgnore = xArg.defaultValue.get
|
||||
.asInstanceOf[IR.Function.Lambda]
|
||||
.arguments
|
||||
.head
|
||||
.asInstanceOf[IR.DefinitionArgument.Specified]
|
||||
|
||||
nestedIgnore.name shouldBe an[IR.Name.Literal]
|
||||
nestedIgnore.getMetadata(IgnoredBindings) shouldEqual Some(State.Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
"Ignored bindings desugaring for bindings" should {
|
||||
implicit val ctx: InlineContext = mkInlineContext
|
||||
|
||||
val ir =
|
||||
"""
|
||||
|_ =
|
||||
| _ = f a b
|
||||
| x = y
|
||||
| 10
|
||||
|""".stripMargin.preprocessExpression.get.desugar
|
||||
.asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
val bindingName = ir.name
|
||||
val bindingBody = ir.expression.asInstanceOf[IR.Expression.Block]
|
||||
|
||||
"replace the ignored binding with a fresh name" in {
|
||||
bindingName shouldBe an[IR.Name.Literal]
|
||||
}
|
||||
|
||||
"mark the binding as ignored if it was" in {
|
||||
ir.getMetadata(IgnoredBindings) shouldEqual Some(State.Ignored)
|
||||
}
|
||||
|
||||
"mark the binding as not ignored if it wasn't" in {
|
||||
val nonIgnored =
|
||||
bindingBody.expressions(1).asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
nonIgnored.getMetadata(IgnoredBindings) shouldEqual Some(
|
||||
State.NotIgnored
|
||||
)
|
||||
}
|
||||
|
||||
"work when deeply nested" in {
|
||||
val ignoredInBlock =
|
||||
bindingBody.expressions.head.asInstanceOf[IR.Expression.Binding]
|
||||
|
||||
ignoredInBlock.name shouldBe an[IR.Name.Literal]
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package org.enso.compiler.test.pass.resolve
|
||||
|
||||
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.desugar.GenerateMethodBodies
|
||||
import org.enso.compiler.pass.resolve.OverloadsResolution
|
||||
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
class OverloadsResolutionTest extends CompilerTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
val passes: List[IRPass] = List(
|
||||
GenerateMethodBodies
|
||||
)
|
||||
|
||||
val passConfiguration: PassConfiguration = PassConfiguration()
|
||||
|
||||
implicit val passManager: PassManager =
|
||||
new PassManager(passes, passConfiguration)
|
||||
|
||||
/** Adds an extension method for resolution on the input IR.
|
||||
*
|
||||
* @param ir the IR to desugar
|
||||
*/
|
||||
implicit class ResolveModule(ir: IR.Module) {
|
||||
|
||||
/** Runs section desugaring on [[ir]].
|
||||
*
|
||||
* @param moduleContext the module context in which the resolution takes
|
||||
* place
|
||||
* @return [[ir]], with all sections desugared
|
||||
*/
|
||||
def resolve(implicit moduleContext: ModuleContext): IR.Module = {
|
||||
OverloadsResolution.runModule(ir, moduleContext)
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes a module context.
|
||||
*
|
||||
* @return a new module context
|
||||
*/
|
||||
def mkModuleContext: ModuleContext = {
|
||||
ModuleContext()
|
||||
}
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Method overload resolution" should {
|
||||
implicit val ctx: ModuleContext = mkModuleContext
|
||||
|
||||
val atomName = "Unit"
|
||||
val methodName = "foo"
|
||||
|
||||
val ir =
|
||||
s"""
|
||||
|$atomName.$methodName = x -> x
|
||||
|$atomName.$methodName = x -> y -> x + y
|
||||
|$atomName.$methodName = 10
|
||||
|""".stripMargin.preprocessModule.resolve
|
||||
|
||||
"detect overloads within a given module" in {
|
||||
exactly(2, ir.bindings) shouldBe an[IR.Error.Redefined.Method]
|
||||
}
|
||||
|
||||
"replace all overloads by an error node" in {
|
||||
ir.bindings(1) shouldBe an[IR.Error.Redefined.Method]
|
||||
ir.bindings(2) shouldBe an[IR.Error.Redefined.Method]
|
||||
val redef1 = ir.bindings(1).asInstanceOf[IR.Error.Redefined.Method]
|
||||
val redef2 = ir.bindings(2).asInstanceOf[IR.Error.Redefined.Method]
|
||||
|
||||
redef1.atomName.name shouldEqual atomName
|
||||
redef2.atomName.name shouldEqual atomName
|
||||
|
||||
redef1.methodName.name shouldEqual methodName
|
||||
redef2.methodName.name shouldEqual methodName
|
||||
}
|
||||
}
|
||||
|
||||
"Atom overload resolution" should {
|
||||
implicit val ctx: ModuleContext = mkModuleContext
|
||||
|
||||
val atomName = "MyAtom"
|
||||
|
||||
val ir =
|
||||
s"""
|
||||
|type $atomName a b c
|
||||
|type $atomName a b
|
||||
|type $atomName a
|
||||
|""".stripMargin.preprocessModule.resolve
|
||||
|
||||
"detect overloads within a given module" in {
|
||||
exactly(2, ir.bindings) shouldBe an[IR.Error.Redefined.Atom]
|
||||
}
|
||||
|
||||
"replace all overloads by an error node" in {
|
||||
ir.bindings(1) shouldBe an[IR.Error.Redefined.Atom]
|
||||
ir.bindings(1)
|
||||
.asInstanceOf[IR.Error.Redefined.Atom]
|
||||
.atomName
|
||||
.name shouldEqual atomName
|
||||
ir.bindings(2) shouldBe an[IR.Error.Redefined.Atom]
|
||||
ir.bindings(2)
|
||||
.asInstanceOf[IR.Error.Redefined.Atom]
|
||||
.atomName
|
||||
.name shouldEqual atomName
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.InterpreterTest
|
||||
|
||||
class LambdaShorthandArgsTest extends InterpreterTest {
|
||||
|
||||
val subject = "Lambda shorthand arguments"
|
||||
|
||||
subject should "work for simple applications" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = a -> b -> c -> a + b - c
|
||||
| g = f _ 5 5
|
||||
| g 10
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
|
||||
subject should "work for named applications" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = a -> b -> a - b
|
||||
| g = f (b = _)
|
||||
| g 10 5
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual -5
|
||||
}
|
||||
|
||||
subject should "work for functions in applications" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| add = a -> b -> a + b
|
||||
| sub = a -> b -> a - b
|
||||
| f = _ 10 5
|
||||
| res1 = f add
|
||||
| res2 = f sub
|
||||
| res1 - res2
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
|
||||
subject should "work with mixfix functions" in {
|
||||
val code =
|
||||
"""
|
||||
|Number.if_then_else = ~t -> ~f -> ifZero this t f
|
||||
|
|
||||
|main =
|
||||
| f = if _ then 10 else 5
|
||||
| res1 = f 0
|
||||
| res2 = f 1
|
||||
| res1 - res2
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 5
|
||||
}
|
||||
|
||||
subject should "work with case expressions" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = case _ of
|
||||
| Cons a b -> 10
|
||||
| Nil -> 0
|
||||
| res1 = f (Cons 1 2)
|
||||
| res2 = f Nil
|
||||
| res2 - res1
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual -10
|
||||
}
|
||||
|
||||
subject should "mean id when used alone" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (x = _) -> x
|
||||
| g = f.call
|
||||
| h = _
|
||||
| res1 = g 10
|
||||
| res2 = h 10
|
||||
| res1 - res2
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 0
|
||||
}
|
||||
|
||||
subject should "work with operators" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (_ + 10)
|
||||
| f 10
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 20
|
||||
}
|
||||
|
||||
subject should "work properly with left operator sections" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (_ -)
|
||||
| f 10 5
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 5
|
||||
}
|
||||
|
||||
subject should "work properly with centre operator sections" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = _ - _
|
||||
| f 10 5
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 5
|
||||
}
|
||||
|
||||
subject should "work properly with right operator sections" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (- _)
|
||||
| f 10 5
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual -5
|
||||
}
|
||||
}
|
@ -158,22 +158,4 @@ class MethodsTest extends InterpreterTest {
|
||||
|
||||
eval(code) shouldEqual 6
|
||||
}
|
||||
|
||||
"Methods" should "not be overloaded on a given atom" in {
|
||||
val code =
|
||||
"""
|
||||
|type MyAtom
|
||||
|
|
||||
|MyAtom.foo = a -> a
|
||||
|MyAtom.foo = a -> b -> a + b
|
||||
|
|
||||
|main = foo MyAtom 1
|
||||
|""".stripMargin
|
||||
|
||||
val msg =
|
||||
"org.enso.interpreter.runtime.error.RedefinedMethodException: Methods " +
|
||||
"cannot be overloaded, but you have tried to overload MyAtom.foo"
|
||||
|
||||
the[InterpreterException] thrownBy eval(code) should have message msg
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.InterpreterTest
|
||||
|
||||
class OperatorSectionsTest extends InterpreterTest {
|
||||
|
||||
"Left operator sections" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (1 +)
|
||||
| f 9
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual 10
|
||||
}
|
||||
|
||||
"Sides operator sections" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (-)
|
||||
| f 1 6
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual -5
|
||||
}
|
||||
|
||||
"Right operator sections" should "work" in {
|
||||
val code =
|
||||
"""
|
||||
|main =
|
||||
| f = (- 10)
|
||||
| f 5
|
||||
|""".stripMargin
|
||||
|
||||
eval(code) shouldEqual -5
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package org.enso.interpreter.test.semantic
|
||||
|
||||
import org.enso.interpreter.test.{InterpreterException, InterpreterTest}
|
||||
import org.enso.polyglot.{LanguageInfo, RuntimeOptions}
|
||||
import org.graalvm.polyglot.Context
|
||||
|
||||
class OverloadsResolutionErrorTest extends InterpreterTest {
|
||||
|
||||
// === Test Setup ===========================================================
|
||||
|
||||
override val ctx: Context = Context
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||
.out(output)
|
||||
.build()
|
||||
|
||||
// === The Tests ============================================================
|
||||
|
||||
"Method overloads" should "result in an error at runtime" in {
|
||||
val code =
|
||||
"""
|
||||
|Unit.foo = 10
|
||||
|Unit.foo = 20
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
|
||||
the[InterpreterException] thrownBy eval(code) should have message
|
||||
"Compilation aborted due to errors."
|
||||
|
||||
val diagnostics = consumeOut
|
||||
diagnostics
|
||||
.filterNot(_.contains("Compiler encountered"))
|
||||
.toSet shouldEqual Set(
|
||||
"Test[3:1-3:13]: Method overloads are not supported: Unit.foo is defined multiple times in this module."
|
||||
)
|
||||
}
|
||||
|
||||
"Atom overloads" should "result in an error at runtime" in {
|
||||
val code =
|
||||
"""
|
||||
|type MyAtom
|
||||
|type MyAtom
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
|
||||
the[InterpreterException] thrownBy eval(code) should have message
|
||||
"Compilation aborted due to errors."
|
||||
|
||||
val diagnostics = consumeOut
|
||||
diagnostics
|
||||
.filterNot(_.contains("Compiler encountered"))
|
||||
.toSet shouldEqual Set(
|
||||
"Test[3:1-3:11]: Redefining atoms is not supported: MyAtom is defined multiple times in this module."
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -22,11 +22,13 @@ class StrictCompileDiagnosticsTest extends InterpreterTest {
|
||||
the[InterpreterException] thrownBy eval(code) should have message
|
||||
"Compilation aborted due to errors."
|
||||
|
||||
val _ :: errors = consumeOut
|
||||
errors.toSet shouldEqual Set(
|
||||
val errors = consumeOut
|
||||
errors.filterNot(_.contains("Compiler encountered")).toSet shouldEqual Set(
|
||||
"Test[2:9-2:10]: Parentheses can't be empty.",
|
||||
"Test[3:5-3:9]: Variable x is being redefined.",
|
||||
"Test[4:9-4:9]: Unrecognized token."
|
||||
"Test[4:9-4:9]: Unrecognized token.",
|
||||
"Test[4:5-4:5]: Unused variable y.",
|
||||
"Test[2:5-2:5]: Unused variable x."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user