Add sections, underscore args and ignores (#716)

This commit is contained in:
Ara Adkins 2020-05-06 19:00:03 +01:00 committed by GitHub
parent 55486e50d3
commit ea23cf6fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3391 additions and 439 deletions

View File

@ -76,6 +76,8 @@ val jsSettings = Seq(
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }
)
scalacOptions in (Compile, console) ~= (_ filterNot (_ == "-Xfatal-warnings"))
// ============================================================================
// === Benchmark Configuration ================================================
// ============================================================================

View File

@ -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_

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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.
*

View File

@ -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")
}
}

View File

@ -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.
*

View File

@ -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"
)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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."
)
}
}

View File

@ -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.
*

View File

@ -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)

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
)
}
}
}

View File

@ -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
)
}
})
}

View File

@ -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,

View File

@ -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
)
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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]
}
}
}

View File

@ -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]
}
}
}

View File

@ -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)
)

View File

@ -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()
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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]
}
}
}

View File

@ -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(_, _, _))
}

View File

@ -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
)

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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]
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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."
)
}
}

View File

@ -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."
)
}
}