Add syntax sugar for method/function defs (#765)

This commit is contained in:
Ara Adkins 2020-05-19 15:43:36 +01:00 committed by GitHub
parent 42efcdb8c6
commit a2fe01d399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1283 additions and 180 deletions

View File

@ -23,5 +23,7 @@ public class MethodNames {
public static class Function {
public static final String EQUALS = "equals";
public static final String GET_SOURCE_START = "get_source_start";
public static final String GET_SOURCE_LENGTH = "get_source_length";
}
}

View File

@ -18,7 +18,7 @@ import org.enso.interpreter.runtime.state.Stateful;
* determined by the API provided by Truffle.
*/
@ReportPolymorphism
@NodeInfo(shortName = "Closure", description = "A root node for Enso closures")
@NodeInfo(shortName = "Closure", description = "A root node for Enso closures.")
public class ClosureRootNode extends EnsoRootNode {
@Child private ExpressionNode body;

View File

@ -216,9 +216,26 @@ public final class Function implements TruffleObject {
@ExportMessage
Object invokeMember(String member, Object... args)
throws ArityException, UnknownIdentifierException, UnsupportedTypeException {
if (member.equals(MethodNames.Function.EQUALS)) {
Object that = Types.extractArguments(args, Object.class);
return this == that;
switch (member) {
case MethodNames.Function.EQUALS:
Object that = Types.extractArguments(args, Object.class);
return this == that;
case MethodNames.Function.GET_SOURCE_START:
{
SourceSection sect = getSourceSection();
if (sect == null) {
return null;
}
return sect.getCharIndex();
}
case MethodNames.Function.GET_SOURCE_LENGTH:
{
SourceSection sect = getSourceSection();
if (sect == null) {
return null;
}
return sect.getCharLength();
}
}
throw UnknownIdentifierException.create(member);
}

View File

@ -48,6 +48,7 @@ class Compiler(
* they nevertheless exist.
*/
val compilerPhaseOrdering: List[IRPass] = List(
FunctionBinding,
GenerateMethodBodies,
SectionsToBinOp,
OperatorToFunction,

View File

@ -4,7 +4,7 @@ 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.compiler.exception.{CompilerError, UnhandledEntity}
import org.enso.interpreter.Constants
import org.enso.syntax.text.AST
@ -124,7 +124,7 @@ object AstToIr {
getIdentifiedLocation(inputAST)
)
}
case AstView.MethodDefinition(targetPath, name, definition) =>
case AstView.MethodDefinition(targetPath, name, args, definition) =>
val (path, pathLoc) = if (targetPath.nonEmpty) {
val pathSegments = targetPath.collect {
case AST.Ident.Cons.any(c) => c
@ -147,12 +147,15 @@ object AstToIr {
}
val nameStr = name match { case AST.Ident.Var.any(name) => name }
Module.Scope.Definition.Method(
Module.Scope.Definition.Method.Binding(
Name.Literal(path, pathLoc.map(IdentifiedLocation(_))),
Name.Literal(nameStr.name, getIdentifiedLocation(nameStr)),
args.map(translateArgumentDefinition(_)),
translateExpression(definition),
getIdentifiedLocation(inputAST)
)
case _ =>
throw new UnhandledEntity(inputAST, "translateModuleSymbol")
}
@ -169,6 +172,9 @@ object AstToIr {
.getOrElse(maybeParensedInput)
inputAST match {
case AST.Def(consName, _, _) =>
IR.Error
.Syntax(inputAST, IR.Error.Syntax.TypeDefinedInline(consName.name))
case AstView.UnaryMinus(expression) =>
expression match {
case AST.Literal.Number(base, number) =>
@ -191,6 +197,13 @@ object AstToIr {
getIdentifiedLocation(inputAST)
)
}
case AstView.FunctionSugar(name, args, body) =>
Function.Binding(
translateIdent(name).asInstanceOf[IR.Name.Literal],
args.map(translateArgumentDefinition(_)),
translateExpression(body),
getIdentifiedLocation(inputAST)
)
case AstView
.SuspendedBlock(name, block @ AstView.Block(lines, lastLine)) =>
Expression.Binding(
@ -205,6 +218,11 @@ object AstToIr {
)
case AstView.Assignment(name, expr) =>
translateBinding(getIdentifiedLocation(inputAST), name, expr)
case AstView.MethodDefinition(_, name, _, _) =>
IR.Error.Syntax(
inputAST,
IR.Error.Syntax.MethodDefinedInline(name.asInstanceOf[AST.Ident].name)
)
case AstView.MethodCall(target, name, args) =>
val (validArguments, hasDefaultsSuspended) =
calculateDefaultsSuspension(args)

View File

@ -142,8 +142,8 @@ object AstView {
if (op == lambdaOpSym) {
left match {
case LambdaParamList(args) => Some((args, right))
case _ => None
case FunctionParamList(args) => Some((args, right))
case _ => None
}
} else {
None
@ -209,7 +209,7 @@ object AstView {
}
}
object LambdaParamList {
object FunctionParamList {
/** Matches on the parameter list of a lambda.
*
@ -249,6 +249,37 @@ object AstView {
}
}
object FunctionSugar {
/** Matches on terms that represent long-form function definitions.
*
* @param ast the structure to try and match on
* @return the name of the function, the list of function arguments, and
* the function body
*/
def unapply(ast: AST): Option[(AST.Ident, List[AST], AST)] = {
ast match {
case AstView.Binding(left, body) =>
left match {
case FunctionParamList(items) =>
if (items.length > 1) {
val fnName = items.head
val args = items.tail
fnName match {
case AST.Ident.Var.any(name) => Some((name, args, body))
case _ => None
}
} else {
None
}
case _ => None
}
case _ => None
}
}
}
object MaybeParensed {
/** Matches on terms that _may_ be surrounded by parentheses.
@ -428,16 +459,18 @@ object AstView {
* arbitrary program expression.
*
* @param ast the structure to try and match on
* @return the path segments of the type reference, the function name, and
* the bound expression
* @return the path segments of the type reference, the function name, the
* arguments to the method, and the bound expression
*/
def unapply(ast: AST): Option[(List[AST], AST, AST)] = {
def unapply(ast: AST): Option[(List[AST], AST, List[AST], AST)] = {
ast match {
case Binding(lhs, rhs) =>
lhs match {
case MethodReference(targetPath, name) =>
Some((targetPath, name, rhs))
case AST.Ident.Var.any(name) => Some((List(), name, rhs))
Some((targetPath, name, List(), rhs))
case MethodBindingLHS(path, methodName, args) =>
Some((path, methodName, args, rhs))
case AST.Ident.Var.any(name) => Some((List(), name, List(), rhs))
case _ =>
None
}
@ -447,6 +480,28 @@ object AstView {
}
}
object MethodBindingLHS {
/** Matches on the left hand side of a sugared method definition.
*
* @param ast the structure to try and match on
* @return the path segments of the type reference, the function name, and
* the arguments
*/
def unapply(ast: AST): Option[(List[AST], AST, List[AST])] = {
ast match {
case SpacedList(MethodReference(path, methodName) :: args) =>
val validArgs = args.forall {
case AstView.FunctionParam(_) => true
case _ => false
}
if (validArgs) Some((path, methodName, args)) else None
case _ => None
}
}
}
object ConsOrVar {
/** Matches any expression that is either the name of a constructor or a

View File

@ -216,15 +216,12 @@ class IRToTruffle(
dataflowInfo
)
val methodFunIsTail = methodDef.body
.unsafeGetMetadata(TailCall, "Method body missing tail call info.")
val funNode = methodDef.body match {
case fn: IR.Function =>
expressionProcessor.processFunctionBody(
fn.arguments,
fn.body,
fn.location,
methodDef.location,
Some(methodDef.methodName.name)
)
case _ =>
@ -233,8 +230,6 @@ class IRToTruffle(
)
}
funNode.setTail(methodFunIsTail)
val function = new RuntimeFunction(
funNode.getCallTarget,
null,
@ -606,21 +601,15 @@ class IRToTruffle(
case Error.InvalidIR(_, _, _) =>
throw new CompilerError("Unexpected Invalid IR during codegen.")
case err: Error.Syntax =>
context.getBuiltins
.syntaxError()
.newInstance(err.message)
context.getBuiltins.syntaxError().newInstance(err.message)
case err: Error.Redefined.Binding =>
context.getBuiltins
.compileError()
.newInstance(err.message)
context.getBuiltins.compileError().newInstance(err.message)
case err: Error.Redefined.Method =>
context.getBuiltins
.compileError()
.newInstance(err.message)
context.getBuiltins.compileError().newInstance(err.message)
case err: Error.Redefined.Atom =>
context.getBuiltins
.compileError()
.newInstance(err.message)
context.getBuiltins.compileError().newInstance(err.message)
case err: Error.Redefined.ThisArg =>
context.getBuiltins.compileError().newInstance(err.message)
}
setLocation(ErrorNode.build(payload), error.location)
}

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,7 @@ import scala.reflect.ClassTag
*
* It must have the following passes run before it:
*
* - [[org.enso.compiler.pass.desugar.FunctionBinding]]
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
@ -129,7 +130,8 @@ case object AliasAnalysis extends IRPass {
val topLevelGraph = new Graph
ir match {
case m @ IR.Module.Scope.Definition.Method(_, _, body, _, _, _) =>
case m @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
body match {
case _: IR.Function =>
m.copy(
@ -147,6 +149,10 @@ case object AliasAnalysis extends IRPass {
"The body of a method should always be a function."
)
}
case _: IR.Module.Scope.Definition.Method.Binding =>
throw new CompilerError(
"Method definition sugar should not occur during alias analysis."
)
case a @ IR.Module.Scope.Definition.Atom(_, args, _, _, _) =>
a.copy(
arguments =
@ -419,6 +425,10 @@ case object AliasAnalysis extends IRPass {
)
)
.updateMetadata(this -->> Info.Scope.Child(graph, currentScope))
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during alias analysis."
)
}
}

View File

@ -21,6 +21,7 @@ import scala.collection.mutable
*
* It must have the following passes run before it:
*
* - [[org.enso.compiler.pass.desugar.FunctionBinding]]
* - [[AliasAnalysis]]
* - [[DemandAnalysis]]
* - [[TailCall]]
@ -97,7 +98,8 @@ case object DataflowAnalysis extends IRPass {
arguments = arguments.map(analyseDefinitionArgument(_, info))
)
.updateMetadata(this -->> info)
case method @ IR.Module.Scope.Definition.Method(_, _, body, _, _, _) =>
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
info.updateAt(asStatic(body), Set(asStatic(method)))
method
@ -105,6 +107,11 @@ case object DataflowAnalysis extends IRPass {
body = analyseExpression(body, info)
)
.updateMetadata(this -->> info)
case _: IR.Module.Scope.Definition.Method.Binding =>
throw new CompilerError(
"Sugared method definitions should not occur during dataflow " +
"analysis."
)
case err: IR.Error.Redefined => err
}
}
@ -187,6 +194,10 @@ case object DataflowAnalysis extends IRPass {
body = analyseExpression(body, info)
)
.updateMetadata(this -->> info)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during alias analysis."
)
}
}

View File

@ -129,6 +129,10 @@ case object DemandAnalysis extends IRPass {
isInsideCallArgument = false
)
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during demand analysis."
)
}
/** Performs demand analysis for a name.

View File

@ -18,6 +18,7 @@ import org.enso.compiler.pass.IRPass
*
* It must have the following passes run before it:
*
* - [[org.enso.compiler.pass.desugar.FunctionBinding]]
* - [[org.enso.compiler.pass.desugar.GenerateMethodBodies]]
* - [[org.enso.compiler.pass.desugar.SectionsToBinOp]]
* - [[org.enso.compiler.pass.desugar.OperatorToFunction]]
@ -76,12 +77,18 @@ case object TailCall extends IRPass {
definition: IR.Module.Scope.Definition
): IR.Module.Scope.Definition = {
definition match {
case method @ IR.Module.Scope.Definition.Method(_, _, body, _, _, _) =>
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
method
.copy(
body = analyseExpression(body, isInTailPosition = true)
)
.updateMetadata(this -->> TailPosition.Tail)
case _: IR.Module.Scope.Definition.Method.Binding =>
throw new CompilerError(
"Sugared method definitions should not occur during tail call " +
"analysis."
)
case atom @ IR.Module.Scope.Definition.Atom(_, args, _, _, _) =>
atom
.copy(
@ -341,6 +348,10 @@ case object TailCall extends IRPass {
arguments = args.map(analyseDefArgument),
body = analyseExpression(body, isInTailPosition = markAsTail)
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during tail call analysis."
)
}
resultFunction.updateMetadata(

View File

@ -0,0 +1,112 @@
package org.enso.compiler.pass.desugar
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Error.Redefined
import org.enso.compiler.core.IR.Module.Scope.Definition
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import scala.annotation.unused
/** This pass handles the desugaring of long-form function and method
* definitions into standard bindings using lambdas.
*
* This works for any definition of the form `f <args> = <body>`.
*
* This pass has no configuration.
*
* This pass requires the context to provide:
*
* - Nothing
*
* It must have the following passes run before it:
*
* - None
*/
//noinspection DuplicatedCode
case object FunctionBinding extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
/** Rusn desugaring of sugared method and function bindings on 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,
@unused moduleContext: ModuleContext
): IR.Module = ir.copy(bindings = ir.bindings.map(desugarModuleSymbol))
/** Runs desugaring of function bindings on 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,
@unused inlineContext: InlineContext
): IR.Expression = desugarExpression(ir)
// === Pass Internals =======================================================
/** Performs desugaring on an arbitrary Enso expression.
*
* @param ir the expression to desugar
* @return `ir`, with any function definition sugar removed
*/
def desugarExpression(ir: IR.Expression): IR.Expression = {
ir.transformExpressions {
case IR.Function.Binding(name, args, body, location, canBeTCO, _, _) =>
if (args.isEmpty) {
throw new CompilerError("The arguments list should not be empty.")
}
val lambda = args
.map(_.mapExpressions(desugarExpression))
.foldRight(desugarExpression(body))((arg, body) =>
IR.Function.Lambda(List(arg), body, None)
)
.asInstanceOf[IR.Function.Lambda]
.copy(canBeTCO = canBeTCO, location = location)
IR.Expression.Binding(name, lambda, location)
}
}
/** Performs desugaring on a module definition.
*
* @param definition the module definition to desugar
* @return `definition`, with any function definition sugar removed
*/
def desugarModuleSymbol(
definition: IR.Module.Scope.Definition
): IR.Module.Scope.Definition = {
definition match {
case a @ Definition.Atom(_, arguments, _, _, _) =>
a.copy(arguments = arguments.map(_.mapExpressions(desugarExpression)))
case _: Method.Explicit =>
throw new CompilerError(
"Explicit method definitions should not exist during function " +
"binding desugaring."
)
case Method.Binding(typeName, methName, args, body, loc, _, _) =>
val newBody = args
.map(_.mapExpressions(desugarExpression))
.foldRight(desugarExpression(body))((arg, body) =>
IR.Function.Lambda(List(arg), body, None)
)
Method.Explicit(typeName, methName, newBody, loc)
case e: Redefined => e
}
}
}

View File

@ -2,6 +2,8 @@ package org.enso.compiler.pass.desugar
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Error.Redefined
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
/** This pass is responsible for ensuring that method bodies are in the correct
@ -19,7 +21,7 @@ import org.enso.compiler.pass.IRPass
*
* It must have the following passes run before it:
*
* - None
* - [[FunctionBinding]]
*/
case object GenerateMethodBodies extends IRPass {
@ -57,12 +59,20 @@ case object GenerateMethodBodies extends IRPass {
def processMethodDef(
ir: IR.Module.Scope.Definition.Method
): IR.Module.Scope.Definition.Method = {
ir.copy(
body = ir.body match {
case fun: IR.Function => processBodyFunction(fun)
case expression => processBodyExpression(expression)
}
)
ir match {
case ir: IR.Module.Scope.Definition.Method.Explicit =>
ir.copy(
body = ir.body match {
case fun: IR.Function => processBodyFunction(fun)
case expression => processBodyExpression(expression)
}
)
case _: IR.Module.Scope.Definition.Method.Binding =>
throw new CompilerError(
"Method definition sugar should not be present during method body " +
"generation."
)
}
}
/** Processes the method body if it's a function.
@ -73,12 +83,25 @@ case object GenerateMethodBodies extends IRPass {
* @param fun the body function
* @return the body function with the `this` argument
*/
def processBodyFunction(fun: IR.Function): IR.Function = {
fun match {
case lam @ IR.Function.Lambda(args, _, _, _, _, _) =>
lam.copy(
arguments = genThisArgument :: args
)
def processBodyFunction(fun: IR.Function): IR.Expression = {
val containsThis = collectChainedFunctionArgs(fun).exists(arg =>
arg.name == IR.Name.This(arg.name.location)
)
if (!containsThis) {
fun match {
case lam @ IR.Function.Lambda(args, _, _, _, _, _) =>
lam.copy(
arguments = genThisArgument :: args
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function definition sugar should not be present during method " +
"body generation."
)
}
} else {
IR.Error.Redefined.ThisArg(fun.location)
}
}
@ -123,4 +146,20 @@ case object GenerateMethodBodies extends IRPass {
ir: IR.Expression,
inlineContext: InlineContext
): IR.Expression = ir
/** Collects the argument list of a chain of function definitions.
*
* @param function the function to collect args for
* @return the list of arguments for `function`
*/
def collectChainedFunctionArgs(
function: IR.Function
): List[IR.DefinitionArgument] = {
val bodyArgs = function.body match {
case f: IR.Function => (collectChainedFunctionArgs(f))
case _ => List()
}
function.arguments ::: bodyArgs
}
}

View File

@ -2,6 +2,7 @@ package org.enso.compiler.pass.lint
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.resolve.IgnoredBindings
@ -114,6 +115,10 @@ case object UnusedBindings extends IRPass {
arguments = args.map(lintFunctionArgument(_, context)),
body = runExpression(body, context)
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during unused bindings linting."
)
}
}

View File

@ -158,6 +158,10 @@ case object LambdaConsolidate extends IRPass {
canBeTCO = chainedLambdas.last.canBeTCO,
passData = MetadataStorage()
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during lambda consolidation."
)
}
}

View File

@ -141,6 +141,11 @@ case object IgnoredBindings extends IRPass {
arguments = newArgs,
body = desugarExpression(body, supply)
)
case _: IR.Function.Binding =>
throw new CompilerError(
"Function sugar should not be present during ignored " +
"bindings desugaring."
)
}
}

View File

@ -163,7 +163,7 @@ trait CompilerRunner {
*/
def asMethod: IR.Module.Scope.Definition.Method = {
IR.Module.Scope.Definition
.Method(
.Method.Explicit(
IR.Name.Literal("TestType", None),
IR.Name.Literal("testMethod", None),
ir,

View File

@ -239,4 +239,93 @@ class AstToIrTest extends CompilerTest {
fooArg.value.asInstanceOf[IR.Name.Literal].name shouldEqual "foo"
}
}
"AST translation of function sugar" should {
"work for function definitions" in {
val ir =
"""
|f a b = a + b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Function.Binding]
}
"work for method definitions" in {
val ir =
"""
|Foo.bar a b = a + b
|""".stripMargin.toIrModule
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Binding]
}
"work for method definitions with involved arguments" in {
val ir =
"""
|Foo.bar _ (b = 1) ~c = b + c
|""".stripMargin.toIrModule
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Binding]
}
"not recognise pattern match bindings" in {
val ir =
"""
|F a b = a + b
|""".stripMargin.toIrExpression.get
ir should not be an[IR.Function.Binding]
}
"work with ignored arguments" in {
val ir =
"""
|f _ b = a + b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Function.Binding]
}
"work with defaulted arguments" in {
val ir =
"""
|f (a = 1) b = a + b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Function.Binding]
}
"work with lazy arguments" in {
val ir =
"""
|f ~a b = a + b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Function.Binding]
}
}
"AST translation for the inline flow" should {
"disallow method definitions without exploding" in {
val ir =
"""
|Unit.foo a b = a + b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Error.Syntax]
ir.asInstanceOf[IR.Error.Syntax]
.reason shouldBe an[IR.Error.Syntax.MethodDefinedInline]
}
"disallow type definitions without explocing" in {
val ir =
"""
|type MyAtom a b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Error.Syntax]
ir.asInstanceOf[IR.Error.Syntax]
.reason shouldBe an[IR.Error.Syntax.TypeDefinedInline]
}
}
}

View File

@ -7,12 +7,7 @@ import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.AliasAnalysis
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,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
@ -22,6 +17,7 @@ class AliasAnalysisTest extends CompilerTest {
/** The passes that need to be run before the alias analysis pass. */
val precursorPasses: List[IRPass] = List(
FunctionBinding,
GenerateMethodBodies,
SectionsToBinOp,
OperatorToFunction,

View File

@ -5,13 +5,8 @@ import org.enso.compiler.core.IR
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo
import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo.Type.asStatic
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.desugar.{GenerateMethodBodies, OperatorToFunction}
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis, DemandAnalysis, TailCall}
import org.enso.compiler.pass.desugar.{FunctionBinding, GenerateMethodBodies, OperatorToFunction}
import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
@ -25,6 +20,7 @@ class DataflowAnalysisTest extends CompilerTest {
/** The passes that must be run before the dataflow analysis pass. */
val precursorPasses: List[IRPass] = List(
FunctionBinding,
GenerateMethodBodies,
OperatorToFunction,
AliasAnalysis,

View File

@ -72,8 +72,10 @@ class GatherDiagnosticsTest extends CompilerTest {
),
None
),
IR.Module.Scope.Definition.Method(typeName, method1Name, lam, None),
IR.Module.Scope.Definition.Method(typeName, method2Name, error3, None)
IR.Module.Scope.Definition.Method
.Explicit(typeName, method1Name, lam, None),
IR.Module.Scope.Definition.Method
.Explicit(typeName, method2Name, error3, None)
),
None
)

View File

@ -6,10 +6,7 @@ import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.analyse.TailCall.TailPosition
import org.enso.compiler.pass.analyse.{AliasAnalysis, TailCall}
import org.enso.compiler.pass.desugar.{
GenerateMethodBodies,
OperatorToFunction
}
import org.enso.compiler.pass.desugar.{FunctionBinding, GenerateMethodBodies, OperatorToFunction}
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.runtime.scope.LocalScope
@ -32,6 +29,7 @@ class TailCallTest extends CompilerTest {
)
val precursorPasses: List[IRPass] = List(
FunctionBinding,
GenerateMethodBodies,
OperatorToFunction,
AliasAnalysis

View File

@ -0,0 +1,196 @@
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.desugar.FunctionBinding
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
class FunctionBindingTest extends CompilerTest {
// === Test Setup ===========================================================
val precursorPasses: List[IRPass] = List()
val passConfig: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(precursorPasses, passConfig)
/** Adds an extension method to run method and function desugaring on an
* [[IR.Module]].
*
* @param ir the module to run desugaring on
*/
implicit class DesugarModule(ir: IR.Module) {
/** Runs desugaring on a module.
*
* @param moduleContext the module context in which desugaring is taking
* place
* @return [[ir]], with any sugared function and method definitions
* desugared
*/
def desugar(implicit moduleContext: ModuleContext): IR.Module = {
FunctionBinding.runModule(ir, moduleContext)
}
}
/** Adds an extension method to run function desugaring on an arbitrary
* expression.
*
* @param ir the expression to desugar
*/
implicit class DesugarExpression(ir: IR.Expression) {
/** Runs desgaring on an expression.
*
* @param inlineContext the inline context in which the desugaring is
* taking place
* @return [[ir]], with any sugared function definitions desugared
*/
def desugar(implicit inlineContext: InlineContext): IR.Expression = {
FunctionBinding.runExpression(ir, inlineContext)
}
}
/** Creates a defaulted module context.
*
* @return a defaulted module context
*/
def mkModuleContext: ModuleContext = {
ModuleContext()
}
/** Creates a defaulted inline context.
*
* @return a defaulted inline context
*/
def mkInlineContext: InlineContext = {
InlineContext()
}
// === The Tests ============================================================
"Sugared method definitions" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|Unit.foo ~a _ (c = 1) = a + c
|""".stripMargin.preprocessModule.desugar
"desugar to standard method definitions" in {
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Explicit]
}
val explicitMethod =
ir.bindings.head.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
"have the function arguments in the body functions" in {
val lambda1 = explicitMethod.body.asInstanceOf[IR.Function.Lambda]
val lambda2 = lambda1.body.asInstanceOf[IR.Function.Lambda]
val lambda3 = lambda2.body.asInstanceOf[IR.Function.Lambda]
val cArg =
lambda3.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
lambda1.arguments.head
.asInstanceOf[IR.DefinitionArgument.Specified]
.suspended shouldEqual true
lambda1.arguments.head.name.name shouldEqual "a"
lambda2.arguments.head
.asInstanceOf[IR.DefinitionArgument.Specified]
.name shouldBe an[IR.Name.Blank]
cArg.name.name shouldEqual "c"
cArg.defaultValue shouldBe defined
}
"desugar nested sugared functions" in {
val ir =
"""
|Foo.bar a =
| f b = b
| f 1
|""".stripMargin.preprocessModule.desugar
val body = ir.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method.Explicit]
.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
body.expressions.head shouldBe an[IR.Expression.Binding]
val binding = body.expressions.head.asInstanceOf[IR.Expression.Binding]
binding.expression shouldBe an[IR.Function.Lambda]
}
}
"Sugared function definitions" should {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|f ~a _ (c = 1) = a + b * c
|""".stripMargin.preprocessExpression.get.desugar
"desugar to a binding with a lambda" in {
ir shouldBe an[IR.Expression.Binding]
val binding = ir.asInstanceOf[IR.Expression.Binding]
binding.name.name shouldEqual "f"
binding.expression shouldBe an[IR.Function.Lambda]
}
"work properly for complex argument definition types" in {
val lambda1 = ir
.asInstanceOf[IR.Expression.Binding]
.expression
.asInstanceOf[IR.Function.Lambda]
val lambda2 = lambda1.body.asInstanceOf[IR.Function.Lambda]
val lambda3 = lambda2.body.asInstanceOf[IR.Function.Lambda]
val cArg =
lambda3.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
lambda1.arguments.head
.asInstanceOf[IR.DefinitionArgument.Specified]
.suspended shouldEqual true
lambda2.arguments.head
.asInstanceOf[IR.DefinitionArgument.Specified]
.name shouldBe an[IR.Name.Blank]
cArg.name.name shouldEqual "c"
cArg.defaultValue shouldBe defined
}
"work recursively" in {
val ir =
"""
|f (a = (f a = a)) =
| g b = b
| g 1
|""".stripMargin.preprocessExpression.get.desugar
.asInstanceOf[IR.Expression.Binding]
val aArg = ir.expression
.asInstanceOf[IR.Function.Lambda]
.arguments
.head
.asInstanceOf[IR.DefinitionArgument.Specified]
aArg.name.name shouldEqual "a"
aArg.defaultValue.get
.asInstanceOf[IR.Expression.Binding]
.name
.name shouldEqual "f"
val body = ir.expression
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
body.expressions.head shouldBe an[IR.Expression.Binding]
val gBinding = body.expressions.head.asInstanceOf[IR.Expression.Binding]
gBinding.name.name shouldEqual "g"
gBinding.expression shouldBe an[IR.Function.Lambda]
}
}
}

View File

@ -1,15 +1,41 @@
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.context.ModuleContext
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.pass.desugar.GenerateMethodBodies
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.pass.desugar.{FunctionBinding, GenerateMethodBodies}
import org.enso.compiler.test.CompilerTest
class GenerateMethodBodiesTest extends CompilerTest {
// === Test Setup ===========================================================
val ctx = ModuleContext()
implicit val ctx: ModuleContext = ModuleContext()
val precursorPasses: List[IRPass] = List(FunctionBinding)
val passConfig: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(precursorPasses, passConfig)
/** Adds an extension method to run method and method body generation on an
* [[IR.Module]].
*
* @param ir the module to run desugaring on
*/
implicit class DesugarModule(ir: IR.Module) {
/** Runs desugaring on a module.
*
* @param moduleContext the module context in which desugaring is taking
* place
* @return [[ir]], with any method bodies desugared
*/
def desugar(implicit moduleContext: ModuleContext): IR.Module = {
GenerateMethodBodies.runModule(ir, moduleContext)
}
}
// === The Tests ============================================================
@ -17,10 +43,10 @@ class GenerateMethodBodiesTest extends CompilerTest {
val ir =
"""
|Unit.method = a -> b -> c -> a + b + c
|""".stripMargin.toIrModule
|""".stripMargin.preprocessModule
val irMethod = ir.bindings.head.asInstanceOf[Method]
val irResult = GenerateMethodBodies.runModule(ir, ctx)
val irResult = ir.desugar
val irResultMethod = irResult.bindings.head.asInstanceOf[Method]
"have the `this` argument prepended to the argument list" in {
@ -48,10 +74,10 @@ class GenerateMethodBodiesTest extends CompilerTest {
val ir =
"""
|Unit.method = 1
|""".stripMargin.toIrModule
|""".stripMargin.preprocessModule
val irMethod = ir.bindings.head.asInstanceOf[Method]
val irResult = GenerateMethodBodies.runModule(ir, ctx)
val irResult = ir.desugar
val irResultMethod = irResult.bindings.head.asInstanceOf[Method]
"have the expression converted into a function" in {
@ -78,4 +104,18 @@ class GenerateMethodBodiesTest extends CompilerTest {
irMethod.body.location shouldEqual irResultMethod.body.location
}
}
"Methods that redefine `this`" should {
val ir =
"""
|Unit.method = this -> this + 1
|""".stripMargin.preprocessModule.desugar
val method =
ir.bindings.head.asInstanceOf[IR.Module.Scope.Definition.Method]
"have their bodies replaced by an error" in {
method.body shouldBe an[IR.Error.Redefined.ThisArg]
}
}
}

View File

@ -1,19 +1,18 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.context.ModuleContext
import org.enso.compiler.core.IR
import org.enso.compiler.pass.desugar.GenerateMethodBodies
import org.enso.compiler.pass.desugar.{FunctionBinding, 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(
FunctionBinding,
GenerateMethodBodies
)
@ -52,15 +51,15 @@ class OverloadsResolutionTest extends CompilerTest {
"Method overload resolution" should {
implicit val ctx: ModuleContext = mkModuleContext
val atomName = "Unit"
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
|$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]
@ -87,10 +86,10 @@ class OverloadsResolutionTest extends CompilerTest {
val ir =
s"""
|type $atomName a b c
|type $atomName a b
|type $atomName a
|""".stripMargin.preprocessModule.resolve
|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]

View File

@ -1,13 +1,15 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.node.callable.{ApplicationNode, SequenceLiteralNode}
import org.enso.interpreter.node.callable.function.CreateFunctionNode
import org.enso.interpreter.node.callable.thunk.ForceNode
import org.enso.interpreter.node.callable.{ApplicationNode, SequenceLiteralNode}
import org.enso.interpreter.node.controlflow.MatchNode
import org.enso.interpreter.node.expression.literal.IntegerLiteralNode
import org.enso.interpreter.node.scope.{AssignmentNode, ReadLocalVariableNode}
import org.enso.interpreter.test.InterpreterTest
import org.enso.polyglot.MethodNames
class CodeLocationsTest extends InterpreterTest {
def debugPrintCodeLocations(code: String): Unit = {
var off = 0
code.linesIterator.toList.foreach { line =>
@ -203,4 +205,38 @@ class CodeLocationsTest extends InterpreterTest {
instrumenter.assertNodeExists(22, 2, classOf[ApplicationNode])
eval(code)
}
"Sugared method definitions" should "get the right locations" in
withLocationsInstrumenter { instrumenter =>
val code =
"""
|Test.foo a b = a * b - a
|
|main = Test.foo 2 3
|""".stripMargin
val mod = executionContext.evalModule(code, "Test")
val tpe = mod.getAssociatedConstructor
val method = mod.getMethod(tpe, "foo")
method.value.invokeMember(MethodNames.Function.GET_SOURCE_START) shouldEqual 1
method.value.invokeMember(MethodNames.Function.GET_SOURCE_LENGTH) shouldEqual 24
instrumenter.assertNodeExists(16, 9, classOf[ApplicationNode])
eval(code)
}
"Sugared function definitions" should "get the right locations" in
withLocationsInstrumenter { instrumenter =>
val code =
"""
|main =
| f a b = a - b
| f 10 20
|""".stripMargin
instrumenter.assertNodeExists(12, 13, classOf[AssignmentNode])
instrumenter.assertNodeExists(20, 5, classOf[ApplicationNode])
eval(code)
}
}

View File

@ -0,0 +1,28 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.InterpreterTest
class FunctionSugarTest extends InterpreterTest {
"Sugared function definitions" should "work" in {
val code =
"""
|main =
| f a b = a - b
| f 10 20
|""".stripMargin
eval(code) shouldEqual -10
}
"Sugared method definitions" should "work" in {
val code =
"""
|Unit.foo a b = a * b - a
|
|main = Unit.foo 2 3
|""".stripMargin
eval(code) shouldEqual 4
}
}