Add support for writing type signatures (#833)

1.  Adds support for type signature syntax.
2.  Lifts the type operators into metdata.
3.  Uses the signatures to determine arg suspension.
This commit is contained in:
Ara Adkins 2020-06-15 17:45:14 +01:00 committed by GitHub
parent df887f20f2
commit dd0f93c328
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 3546 additions and 575 deletions

View File

@ -179,6 +179,17 @@ layout of the code has no impact on semantics of the code:
x.do_thing
```
Alternatively, it is sufficient to type the binding for the block as
`Suspended a` where `a` is the type of the block.
```ruby
susp : Suspended a =
x = foo x y z
x.do_thing
```
It should be noted that this does not yet work.
The following rules apply to code blocks:
- Code blocks are desugared into in-order applications of monadic bind (as in

View File

@ -75,6 +75,7 @@ pattern context. The following spans are pattern contexts:
- The left-hand-side of the assignment operator (`=`).
- The right-hand-side of the ascription operator (`:`).
- The left-hand-side of the arrow operator (`->`).
- Within the curly braces `{}` delimiting a typeset literal.
The following behaviours occur within a pattern context:

View File

@ -78,9 +78,9 @@ working with types. These are listed below.
| `<:` | `> !`, `< \|`, `> in` | 4 | Left | Asserts that the left operand is structurally subsumed by the right. |
| `~` | `== <:` | 4 | Left | Asserts that the left and right operands are structurally equal. |
| `;` | `< :`, `> =` | -2 | Left | Concatenates the left and right operand typesets to create a new typeset. |
| `\|` | `> <:`, `> !`, `> in`, `> :` | 6 | Left | Computes the union of the left and right operand typesets. |
| `&` | `= \|` | 6 | Left | Computes the intersection of the left and right operand typesets. |
| `\` | `< \|`, `> <:` | 5 | Left | Computes the subtraction of the right typeset from the left typeset. |
| `\|` | `> <:`, `> !`, `> in`, `> :` | 5 | Left | Computes the union of the left and right operand typesets. |
| `&` | `> \|` | 6 | Left | Computes the intersection of the left and right operand typesets. |
| `\` | `> &` | 7 | Left | Computes the subtraction of the right typeset from the left typeset. |
| `:=` | `< :`, `> =`, `> ;` | -1 | Left | Creates a typeset member by assigning a value to a label. |
Solving this set of inequalities produces the _relative_ precedence levels for
@ -118,9 +118,8 @@ bind (`=`) has a relative level of -3 in this ordering.
(assert (> tsUnion err))
(assert (> tsUnion in))
(assert (> tsUnion ascrip))
(assert (= tsInter tsUnion))
(assert (< minus tsUnion))
(assert (> minus sub))
(assert (> tsInter tsUnion))
(assert (> minus tsInter))
(assert (< tsMember ascrip))
(assert (> tsMember bind))
(assert (> tsMember tsConcat))
@ -152,6 +151,9 @@ These work as follows.
label : Type := value
```
Please note that the right-hand-side of the `:=` operator is _not_ a pattern
context.
- **Member Concatenation:** Members can be combined into a typeset using the
concatenation operator `;`.
@ -159,8 +161,10 @@ These work as follows.
x ; y
```
- **Typeset Literals:** A literal can be written using curly braces (`{}`) to
delimit the literal.
- **Typeset Literals:** A typeset literal consists of zero or more typeset
member definitions concatenated while surrounded by curly braces `{}`. The
braces are necessary as they delimit a pattern context to allow the
introduction of new identifiers.
```ruby
{ x: T ; y: Q }

View File

@ -97,7 +97,7 @@ The other key notion of typesets is that typesets are matched _structurally_,
subject to the rules for nominal typing of atoms discussed above.
- Typeset members are themselves typesets.
- A typeset member _must_ have a label, but may also have a type and a value
- A typeset member _must_ have a label, but may also have a type and a value
(`label : Type := value`)
- An unspecified type is considered to be a free type variable.
- The label and the type become part of the typing judgement where present, and
@ -165,8 +165,8 @@ Just.nothing = not isJust
### Typeset Operators
Enso defines a set of operations on typesets that can be used to combine and
manipulate them. Any use of these operators introduces typing evidence which
may later be discharged through pattern matching.
manipulate them. Any use of these operators introduces typing evidence which
may later be discharged through pattern matching.
They are as follows:

View File

@ -9,11 +9,7 @@ import org.enso.compiler.pass.optimise.{
LambdaConsolidate,
UnreachableMatchBranches
}
import org.enso.compiler.pass.resolve.{
DocumentationComments,
IgnoredBindings,
OverloadsResolution
}
import org.enso.compiler.pass.resolve._
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
class Passes(passes: Option[List[IRPass]] = None) {
@ -37,8 +33,12 @@ class Passes(passes: Option[List[IRPass]] = None) {
UnreachableMatchBranches,
NestedPatternMatch,
IgnoredBindings,
TypeFunctions,
TypeSignatures,
AliasAnalysis,
LambdaConsolidate,
AliasAnalysis,
SuspendedArguments,
OverloadsResolution,
AliasAnalysis,
DemandAnalysis,

View File

@ -3,9 +3,9 @@ package org.enso.compiler.codegen
import cats.Foldable
import cats.implicits._
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Name.MethodReference
import org.enso.compiler.core.IR._
import org.enso.compiler.exception.UnhandledEntity
import org.enso.interpreter.Constants
import org.enso.syntax.text.AST
import scala.annotation.tailrec
@ -142,45 +142,89 @@ object AstToIr {
Error.Syntax(inputAst, Error.Syntax.InvalidTypeDefinition)
}
case AstView.MethodDefinition(targetPath, name, args, definition) =>
val (path, pathLoc) = if (targetPath.nonEmpty) {
val nameStr = name match { case AST.Ident.Var.any(name) => name }
val methodRef = if (targetPath.nonEmpty) {
val pathSegments = targetPath.collect {
case AST.Ident.Cons.any(c) => c
}
val pathNames = pathSegments.map(c =>
IR.Name.Literal(c.name, getIdentifiedLocation(c))
)
val pathStr = pathSegments.map(_.name).mkString(".")
val loc = pathSegments.headOption
.flatMap(_.location)
.flatMap(locationStart =>
pathSegments.lastOption
.flatMap(_.location)
.flatMap(locationEnd =>
Some(locationStart.copy(end = locationEnd.end))
)
val methodSegments = pathNames :+ Name.Literal(
nameStr.name,
getIdentifiedLocation(nameStr)
)
(pathStr, loc)
Name.MethodReference(
methodSegments.init,
methodSegments.last,
MethodReference.genLocation(methodSegments)
)
} else {
(Constants.Names.CURRENT_MODULE, None)
val methodSegments = List(
Name.Here(None),
Name.Literal(nameStr.name, getIdentifiedLocation(nameStr))
)
Name.MethodReference(
List(methodSegments.head),
methodSegments.last,
MethodReference.genLocation(methodSegments)
)
}
val nameStr = name match { case AST.Ident.Var.any(name) => name }
Module.Scope.Definition.Method.Binding(
Name.Literal(path, pathLoc.map(IdentifiedLocation(_))),
Name.Literal(nameStr.name, getIdentifiedLocation(nameStr)),
methodRef,
args.map(translateArgumentDefinition(_)),
translateExpression(definition),
getIdentifiedLocation(inputAst)
)
case AstView.FunctionSugar(name, args, body) =>
Module.Scope.Definition.Method.Binding(
val methodSegments = List(
Name.Here(None),
Name.Literal(name.name, getIdentifiedLocation(name)),
Name.Literal(name.name, getIdentifiedLocation(name))
)
val methodReference = Name.MethodReference(
List(methodSegments.head),
methodSegments.last,
MethodReference.genLocation(methodSegments)
)
Module.Scope.Definition.Method.Binding(
methodReference,
args.map(translateArgumentDefinition(_)),
translateExpression(body),
getIdentifiedLocation(inputAst)
)
case AST.Comment.any(comment) => translateComment(comment)
case AstView.TypeAscription(typed, sig) =>
typed match {
case AST.Ident.any(ident) =>
val methodSegments = List(
Name.Here(None),
Name.Literal(ident.name, getIdentifiedLocation(ident))
)
val methodReference = Name.MethodReference(
List(methodSegments.head),
methodSegments.last,
MethodReference.genLocation(methodSegments)
)
IR.Type.Ascription(
methodReference,
translateExpression(sig),
getIdentifiedLocation(inputAst)
)
case AstView.MethodReference(_, _) =>
IR.Type.Ascription(
translateMethodReference(typed),
translateExpression(sig),
getIdentifiedLocation(inputAst)
)
case _ => Error.Syntax(typed, Error.Syntax.InvalidStandaloneSignature)
}
case _ =>
throw new UnhandledEntity(inputAst, "translateModuleSymbol")
}
@ -222,6 +266,12 @@ object AstToIr {
case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs)
case AST.Comment.any(inputAST) => translateComment(inputAST)
case AstView.TypeAscription(typed, sig) =>
IR.Type.Ascription(
translateExpression(typed),
translateExpression(sig),
getIdentifiedLocation(inputAst)
)
case assignment @ AstView.BasicAssignment(_, _) =>
translateExpression(assignment)
case _ =>
@ -229,6 +279,23 @@ object AstToIr {
}
}
/** Translates a method reference from [[AST]] into [[IR]].
*
* @param inputAst the method reference to translate
* @return the [[IR]] representation of `inputAst`
*/
def translateMethodReference(inputAst: AST): IR.Name.MethodReference = {
inputAst match {
case AstView.MethodReference(path, methodName) =>
IR.Name.MethodReference(
path.map(translateExpression(_).asInstanceOf[IR.Name]),
translateExpression(methodName).asInstanceOf[IR.Name],
getIdentifiedLocation(inputAst)
)
case _ => throw new UnhandledEntity(inputAst, "translateMethodReference")
}
}
/** Translates an arbitrary program expression from [[AST]] into [[IR]].
*
* @param maybeParensedInput the expresion to be translated
@ -316,6 +383,11 @@ object AstToIr {
case AST.Literal.any(inputAST) => translateLiteral(inputAST)
case AST.Group.any(inputAST) => translateGroup(inputAST)
case AST.Ident.any(inputAST) => translateIdent(inputAST)
case AST.TypesetLiteral.any(tSet) =>
IR.Application.Literal.Typeset(
tSet.expression.map(translateExpression),
getIdentifiedLocation(tSet)
)
case AST.SequenceLiteral.any(inputAST) =>
translateSequenceLiteral(inputAST)
case AstView.Block(lines, retLine) =>
@ -518,12 +590,6 @@ object AstToIr {
*/
def translateApplicationLike(callable: AST): Expression = {
callable match {
case AstView.ContextAscription(expr, context) =>
Type.Context(
translateExpression(expr),
translateExpression(context),
getIdentifiedLocation(callable)
)
case AstView.Application(name, args) =>
val (validArguments, hasDefaultsSuspended) =
calculateDefaultsSuspension(args)

View File

@ -183,25 +183,6 @@ object AstView {
}
}
object ContextAscription {
/** Matches a usage of the `in` keyword for ascribing a monadic context to
* an expression.
*
* @param ast the ast structure to match on
* @return a pair containing the expression and the context
*/
def unapply(ast: AST): Option[(AST, AST)] = {
ast match {
case MaybeParensed(
AST.App.Prefix(expr, AST.App.Prefix(AST.Ident.Var("in"), context))
) =>
Some((expr, context))
case _ => None
}
}
}
object LazyArgument {
/** Matches on a lazy argument definition or usage.
@ -791,4 +772,18 @@ object AstView {
case _ => None
}
}
object TypeAscription {
/** Matches a usage of the type ascription operator `:`.
*
* @param ast the structure to try and match on
* @return the typed expression, and the ascribed type
*/
def unapply(ast: AST): Option[(AST, AST)] = ast match {
case MaybeParensed(AST.App.Infix(typed, AST.Ident.Opr(":"), sig)) =>
Some((typed, sig))
case _ => None
}
}
}

View File

@ -5,16 +5,11 @@ import com.oracle.truffle.api.source.{Source, SourceSection}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Import
import org.enso.compiler.core.IR.{Error, IdentifiedLocation, Pattern}
import org.enso.compiler.exception.{
BadPatternMatch,
CompilerError,
UnhandledEntity
}
import org.enso.compiler.exception.{BadPatternMatch, CompilerError}
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,
CachePreferenceAnalysis,
DataflowAnalysis,
TailCall
}
@ -345,7 +340,7 @@ class IrToTruffle(
* @param ir the IR to generate code for
* @return a truffle expression that represents the same program as `ir`
*/
def run(ir: IR): RuntimeExpression = {
def run(ir: IR.Expression): RuntimeExpression = {
val tailMeta = ir.unsafeGetMetadata(
TailCall,
"Missing tail call information on method."
@ -359,6 +354,11 @@ class IrToTruffle(
case function: IR.Function => processFunction(function)
case binding: IR.Expression.Binding => processBinding(binding)
case caseExpr: IR.Case => processCase(caseExpr)
case typ: IR.Type => processType(typ)
case _: IR.Empty =>
throw new CompilerError(
"Empty IR nodes should not exist during code generation."
)
case _: IR.Comment =>
throw new CompilerError(
"Comments should not be present during codegen."
@ -368,7 +368,6 @@ class IrToTruffle(
throw new CompilerError(
s"Foreign expressions not yet implemented: $ir."
)
case _ => throw new UnhandledEntity(ir, "run")
}
runtimeExpression.setTail(tailMeta)
@ -428,7 +427,25 @@ class IrToTruffle(
}
}
/** Performs code generation for Enso case expression.
/** Performs code generation for an Enso type operator.
*
* @param value the type operation to generate code for
* @return the truffle nodes corresponding to `value`
*/
def processType(value: IR.Type): RuntimeExpression = {
setLocation(
ErrorNode.build(
context.getBuiltins
.syntaxError()
.newInstance(
"Type operators are not currently supported at runtime."
)
),
value.location
)
}
/** Performs code generation for an Enso case expression.
*
* @param caseExpr the case expression to generate code for
* @return the truffle nodes corresponding to `caseExpr`
@ -437,7 +454,7 @@ class IrToTruffle(
case IR.Case.Expr(scrutinee, branches, location, _, _) =>
val scrutineeNode = this.run(scrutinee)
val maybeCases = branches.map(processCaseBranch)
val maybeCases = branches.map(processCaseBranch)
val allCasesValid = maybeCases.forall(_.isRight)
// TODO [AA] This is until we can resolve this statically in the
@ -676,6 +693,10 @@ class IrToTruffle(
throw new CompilerError(
"Blanks should not be present at codegen time."
)
case _: IR.Name.MethodReference =>
throw new CompilerError(
"Method references should not be present at codegen time."
)
}
setLocation(nameExpr, name.location)
@ -714,6 +735,8 @@ class IrToTruffle(
context.getBuiltins.compileError().newInstance(err.message)
case err: Error.Redefined.ThisArg =>
context.getBuiltins.compileError().newInstance(err.message)
case err: Error.Unexpected.TypeSignature =>
context.getBuiltins.compileError().newInstance(err.message)
}
setLocation(ErrorNode.build(payload), error.location)
}
@ -860,6 +883,17 @@ class IrToTruffle(
case IR.Application.Literal.Sequence(items, location, _, _) =>
val itemNodes = items.map(run).toArray
setLocation(SequenceLiteralNode.build(itemNodes), location)
case _: IR.Application.Literal.Typeset =>
setLocation(
ErrorNode.build(
context.getBuiltins
.syntaxError()
.newInstance(
"Typeset literals are not yet supported at runtime."
)
),
application.location
)
case op: IR.Application.Operator.Binary =>
throw new CompilerError(
s"Explicit operators not supported during codegen but $op found"

File diff suppressed because it is too large Load Diff

View File

@ -137,7 +137,7 @@ case object AliasAnalysis extends IRPass {
ir match {
case m @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
.Explicit(_, body, _, _, _) =>
body match {
case _: IR.Function =>
m.copy(
@ -173,6 +173,11 @@ case object AliasAnalysis extends IRPass {
throw new CompilerError(
"Documentation should not exist as an entity during alias analysis."
)
case _: IR.Type.Ascription =>
throw new CompilerError(
"Type signatures should not exist at the top level during " +
"alias analysis."
)
case err: IR.Error => err
}
}
@ -196,7 +201,7 @@ case object AliasAnalysis extends IRPass {
expression: IR.Expression,
graph: Graph,
parentScope: Scope,
lambdaReuseScope: Boolean = false,
lambdaReuseScope: Boolean = false
): IR.Expression = {
expression match {
case fn: IR.Function =>
@ -379,6 +384,11 @@ case object AliasAnalysis extends IRPass {
app.copy(target = analyseExpression(expr, graph, scope))
case app @ IR.Application.Literal.Sequence(items, _, _, _) =>
app.copy(items = items.map(analyseExpression(_, graph, scope)))
case tSet @ IR.Application.Literal.Typeset(expr, _, _, _) =>
val newScope = scope.addChild()
tSet
.copy(expression = expr.map(analyseExpression(_, graph, newScope)))
.updateMetadata(this -->> Info.Scope.Child(graph, newScope))
case _: IR.Application.Operator.Binary =>
throw new CompilerError(
"Binary operator occurred during Alias Analysis."
@ -441,7 +451,7 @@ case object AliasAnalysis extends IRPass {
body = analyseExpression(
body,
graph,
currentScope,
currentScope
)
)
.updateMetadata(this -->> Info.Scope.Child(graph, currentScope))
@ -527,7 +537,7 @@ case object AliasAnalysis extends IRPass {
expression = analyseExpression(
branch.expression,
graph,
currentScope,
currentScope
)
)
.updateMetadata(this -->> Info.Scope.Child(graph, currentScope))

View File

@ -93,7 +93,7 @@ case object CachePreferenceAnalysis extends IRPass {
)
.updateMetadata(this -->> weights)
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
.Explicit(_, body, _, _, _) =>
method
.copy(body = analyseExpression(body, weights))
.updateMetadata(this -->> weights)
@ -109,9 +109,14 @@ case object CachePreferenceAnalysis extends IRPass {
)
case _: IR.Comment.Documentation =>
throw new CompilerError(
"Documentation should not exist as an entity during cacache " +
"Documentation should not exist as an entity during cache " +
"preference analysis."
)
case _: IR.Type.Ascription =>
throw new CompilerError(
"Type signatures should not exist at the top level during " +
"cache preference analysis."
)
case err: IR.Error => err
}

View File

@ -100,7 +100,7 @@ case object DataflowAnalysis extends IRPass {
)
.updateMetadata(this -->> info)
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
.Explicit(_, body, _, _, _) =>
info.updateAt(asStatic(body), Set(asStatic(method)))
method
@ -122,6 +122,11 @@ case object DataflowAnalysis extends IRPass {
throw new CompilerError(
"Documentation should not exist as an entity during dataflow analysis."
)
case _: IR.Type.Ascription =>
throw new CompilerError(
"Type signatures should not exist at the top level during " +
"dataflow analysis."
)
case err: IR.Error => err
}
}
@ -250,6 +255,12 @@ case object DataflowAnalysis extends IRPass {
vector
.copy(items = items.map(analyseExpression(_, info)))
.updateMetadata(this -->> info)
case tSet @ IR.Application.Literal.Typeset(expr, _, _, _) =>
expr.foreach(exp => info.updateAt(asStatic(exp), Set(asStatic(tSet))))
tSet
.copy(expression = expr.map(analyseExpression(_, info)))
.updateMetadata(this -->> info)
case _: IR.Application.Operator =>
throw new CompilerError("Unexpected operator during Dataflow Analysis.")
}
@ -285,6 +296,16 @@ case object DataflowAnalysis extends IRPass {
context = analyseExpression(context, info)
)
.updateMetadata(this -->> info)
case err @ IR.Type.Error(typed, error, _, _, _) =>
info.updateAt(asStatic(typed), Set(asStatic(err)))
info.updateAt(asStatic(error), Set(asStatic(err)))
err
.copy(
typed = analyseExpression(typed, info),
error = analyseExpression(error, info)
)
.updateMetadata(this -->> info)
case member @ IR.Type.Set.Member(_, memberType, value, _, _, _) =>
info.updateAt(asStatic(memberType), Set(asStatic(member)))
info.updateAt(asStatic(value), Set(asStatic(member)))

View File

@ -162,6 +162,11 @@ 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.MethodReference =>
throw new CompilerError(
"Method references should not be present by the time demand " +
"analysis runs."
)
case _: IR.Name.Blank =>
throw new CompilerError(
"Blanks should not be present by the time demand analysis runs."
@ -210,9 +215,14 @@ case object DemandAnalysis extends IRPass {
)
)
)
case _ =>
case tSet @ IR.Application.Literal.Typeset(expr, _, _, _) =>
tSet.copy(
expression =
expr.map(analyseExpression(_, isInsideCallArgument = false))
)
case _: IR.Application.Operator =>
throw new CompilerError(
"Unexpected application type during demand analysis."
"Operators should not be present during demand analysis."
)
}

View File

@ -82,7 +82,7 @@ case object TailCall extends IRPass {
): IR.Module.Scope.Definition = {
definition match {
case method @ IR.Module.Scope.Definition.Method
.Explicit(_, _, body, _, _, _) =>
.Explicit(_, body, _, _, _) =>
method
.copy(
body = analyseExpression(body, isInTailPosition = true)
@ -108,6 +108,11 @@ case object TailCall extends IRPass {
throw new CompilerError(
"Documentation should not exist as an entity during tail call analysis."
)
case _: IR.Type.Ascription =>
throw new CompilerError(
"Type signatures should not exist at the top level during " +
"tail call analysis."
)
case err: IR.Error => err
}
}
@ -212,6 +217,12 @@ case object TailCall extends IRPass {
items.map(analyseExpression(_, isInTailPosition = false))
)
.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
case tSet @ IR.Application.Literal.Typeset(expr, _, _, _) =>
tSet
.copy(expression =
expr.map(analyseExpression(_, isInTailPosition = false))
)
.updateMetadata(this -->> TailPosition.fromBool(isInTailPosition))
case _: IR.Application.Operator =>
throw new CompilerError("Unexpected binary operator.")
}

View File

@ -2,8 +2,10 @@ 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.IdentifiedLocation
import org.enso.compiler.core.IR.Module.Scope.Definition
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.IR.Name.MethodReference
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.{
@ -12,6 +14,7 @@ import org.enso.compiler.pass.analyse.{
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.desugar.ComplexType.genMethodDef
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
@ -105,32 +108,88 @@ case object ComplexType extends IRPass {
case n: IR.Name => n
}
val namesToDefineMethodsOn = atomIncludes ++ atomDefs.map(_.name)
val methods = typ.body.collect {
case b: IR.Expression.Binding => b
case f: IR.Function.Binding => f
val remainingEntities = typ.body.filterNot {
case _: IR.Module.Scope.Definition.Atom => true
case _: IR.Name => true
case _ => false
}
if ((atomDefs ::: atomIncludes ::: methods).length != typ.body.length) {
throw new CompilerError(
"All bindings in a type definition body should be accounted for."
var lastSignature: Option[IR.Type.Ascription] = None
/** Pairs up signatures with method definitions, and then generates the
* appropriate method definitions for the atoms in scope.
*
* @param name the name of the method
* @param defn the definition of the method
* @return a list of method definitions for `name`
*/
def matchSignaturesAndGenerate(
name: IR.Name,
defn: IR
): List[IR.Module.Scope.Definition] = {
var unusedSig: Option[IR.Type.Ascription] = None
val sig = lastSignature match {
case Some(IR.Type.Ascription(typed, _, _, _, _)) =>
typed match {
case IR.Name.Literal(nameStr, _, _, _) =>
if (name.name == nameStr) {
lastSignature
} else {
unusedSig = lastSignature
None
}
case _ =>
unusedSig = lastSignature
None
}
case None => None
}
lastSignature = None
val unusedList: List[Definition] = unusedSig.toList
unusedList ::: genMethodDef(
defn,
namesToDefineMethodsOn,
sig
)
}
val methodDefs = methods.flatMap(genMethodDef(_, namesToDefineMethodsOn))
val entityResults: List[Definition] = remainingEntities.flatMap {
case sig: IR.Type.Ascription =>
val res = lastSignature match {
case Some(oldSig) => Some(oldSig)
case None => None
}
atomDefs ::: methodDefs
lastSignature = Some(sig)
res
case binding @ IR.Expression.Binding(name, _, _, _, _) =>
matchSignaturesAndGenerate(name, binding)
case funSugar @ IR.Function.Binding(name, _, _, _, _, _, _) =>
matchSignaturesAndGenerate(name, funSugar)
case _ =>
throw new CompilerError("Unexpected IR node in complex type body.")
}
val allEntities = entityResults ::: lastSignature.toList
atomDefs ::: allEntities
}
/** Generates a method definition from a definition in complex type def body.
*
* The signature _must_ correctly match the method definition.
*
* @param ir the definition to generate a method from
* @param names the names on which the method is being defined
* @param signature the type signature for the method, if it exists
* @return `ir` as a method
*/
def genMethodDef(
ir: IR,
names: List[IR.Name]
): List[IR.Module.Scope.Definition.Method] = {
names: List[IR.Name],
signature: Option[IR.Type.Ascription]
): List[IR.Module.Scope.Definition] = {
ir match {
case IR.Expression.Binding(name, expr, location, _, _) =>
val realExpr = expr match {
@ -139,17 +198,60 @@ case object ComplexType extends IRPass {
case _ => expr
}
names.map(typeName => {
Method.Binding(typeName, name, List(), realExpr, location)
})
names.flatMap(
genForName(_, name, List(), realExpr, location, signature)
)
case IR.Function.Binding(name, args, body, location, _, _, _) =>
names.map(typeName => {
Method.Binding(typeName, name, args, body, location)
})
names.flatMap(
genForName(_, name, args, body, location, signature)
)
case _ =>
throw new CompilerError(
"Unexpected IR node during complex type desugaring."
)
}
}
/** Generates a top-level method definition for the provided parameters.
*
* @param typeName the type name the method is being defined on
* @param name the method being defined
* @param args the definition arguments to the method
* @param body the body of the method
* @param location the source location of the method
* @param signature the method's type signature, if it exists
* @return a top-level method definition
*/
def genForName(
typeName: IR.Name,
name: IR.Name,
args: List[IR.DefinitionArgument],
body: IR.Expression,
location: Option[IdentifiedLocation],
signature: Option[IR.Type.Ascription]
): List[IR.Module.Scope.Definition] = {
val methodRef = IR.Name.MethodReference(
List(typeName),
name,
MethodReference.genLocation(List(typeName, name))
)
val newSig =
signature.map(sig =>
sig
.copy(typed =
methodRef.duplicate(keepMetadata = false, keepDiagnostics = false)
)
.duplicate(keepMetadata = false, keepDiagnostics = false)
)
val binding = Method.Binding(
methodRef.duplicate(keepMetadata = false, keepDiagnostics = false),
args,
body.duplicate(keepMetadata = false, keepDiagnostics = false),
location
)
newSig.toList :+ binding
}
}

View File

@ -6,7 +6,12 @@ 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 org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis, DemandAnalysis, TailCall}
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.compiler.pass.resolve.IgnoredBindings
@ -111,14 +116,14 @@ case object FunctionBinding extends IRPass {
"Explicit method definitions should not exist during function " +
"binding desugaring."
)
case Method.Binding(typeName, methName, args, body, loc, _, _) =>
case Method.Binding(methRef, 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)
Method.Explicit(methRef, newBody, loc)
case _: IR.Module.Scope.Definition.Type =>
throw new CompilerError(
"Complex type definitions should not be present during " +
@ -129,7 +134,8 @@ case object FunctionBinding extends IRPass {
"Documentation should not be present during function binding" +
"desugaring."
)
case e: IR.Error => e
case a: IR.Type.Ascription => a
case e: IR.Error => e
}
}
}

View File

@ -244,6 +244,8 @@ case object LambdaShorthandToLambda extends IRPass {
)
IR.Function.Lambda(List(defArg), body, locWithoutId)
}
case tSet @ IR.Application.Literal.Typeset(expr, _, _, _) =>
tSet.copy(expression = expr.map(desugarExpression(_, freshNameSupply)))
case _: IR.Application.Operator =>
throw new CompilerError(
"Operators should be desugared by the point of underscore " +

View File

@ -180,7 +180,10 @@ case object NestedPatternMatch extends IRPass {
val scrutineeBinding =
IR.Expression.Binding(scrutineeBindingName, scrutineeExpression, None)
val caseExprScrutinee = scrutineeBindingName.duplicate()
val caseExprScrutinee = scrutineeBindingName.duplicate(
keepDiagnostics = false,
keepMetadata = false
)
val processedBranches = branches.zipWithIndex.map {
case (branch, ix) =>
@ -234,9 +237,10 @@ case object NestedPatternMatch extends IRPass {
val (lastNestedPattern, nestedPosition) =
fields.zipWithIndex.findLast { case (pat, _) => isNested(pat) }.get
val newName = freshNameSupply.newName()
val newField = Pattern.Name(newName, None)
val nestedScrutinee = newName.duplicate()
val newName = freshNameSupply.newName()
val newField = Pattern.Name(newName, None)
val nestedScrutinee =
newName.duplicate(keepDiagnostics = false, keepMetadata = false)
val newFields =
fields.take(nestedPosition) ++ (newField :: fields.drop(
@ -244,7 +248,8 @@ case object NestedPatternMatch extends IRPass {
))
val newPattern = cons.copy(
fields = newFields.duplicate()
fields =
newFields.duplicate(keepDiagnostics = false, keepMetadata = false)
)
val newExpression = generateNestedCase(
@ -256,8 +261,10 @@ case object NestedPatternMatch extends IRPass {
)
val partDesugaredBranch = IR.Case.Branch(
pattern = newPattern.duplicate(),
expression = newExpression.duplicate(),
pattern = newPattern
.duplicate(keepDiagnostics = false, keepMetadata = false),
expression = newExpression
.duplicate(keepDiagnostics = false, keepMetadata = false),
None
)
@ -314,13 +321,17 @@ case object NestedPatternMatch extends IRPass {
remainingBranches: List[IR.Case.Branch]
): IR.Expression = {
val fallbackCase = IR.Case.Expr(
topLevelScrutineeExpr.duplicate(),
remainingBranches.duplicate(),
topLevelScrutineeExpr.duplicate(keepDiagnostics = false),
remainingBranches.duplicate(keepMetadata = false),
None
)
val patternBranch =
IR.Case.Branch(pattern.duplicate(), currentBranchExpr.duplicate(), None)
IR.Case.Branch(
pattern.duplicate(keepDiagnostics = false),
currentBranchExpr.duplicate(keepMetadata = false),
None
)
val fallbackBranch = IR.Case.Branch(
IR.Pattern.Name(IR.Name.Blank(None), None),
fallbackCase,
@ -328,7 +339,7 @@ case object NestedPatternMatch extends IRPass {
)
IR.Case.Expr(
nestedScrutinee.duplicate(),
nestedScrutinee.duplicate(keepDiagnostics = false, keepMetadata = false),
List(patternBranch, fallbackBranch),
None
)

View File

@ -5,12 +5,8 @@ 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
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.lint.UnusedBindings
/** This pass converts operator sections to applications of binary operators.
*
@ -29,9 +25,11 @@ case object SectionsToBinOp extends IRPass {
)
override val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
TailCall,
UnusedBindings
)
/** Performs section to binary operator conversion on an IR module.
@ -94,21 +92,95 @@ case object SectionsToBinOp extends IRPass {
): IR.Expression = {
section match {
case Section.Left(arg, op, loc, passData, diagnostics) =>
IR.Application.Prefix(
op,
List(arg),
hasDefaultsSuspended = false,
loc,
passData,
diagnostics
val rightArgName = freshNameSupply.newName()
val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None)
val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.duplicate(
keepLocations = false,
keepDiagnostics = false,
keepMetadata = false
),
None,
suspended = false,
None
)
if (arg.value.isInstanceOf[IR.Name.Blank]) {
val leftArgName = freshNameSupply.newName()
val leftCallArg =
IR.CallArgument.Specified(None, leftArgName, None, None)
val leftDefArg = IR.DefinitionArgument.Specified(
leftArgName.duplicate(
keepLocations = false,
keepDiagnostics = false,
keepMetadata = false
),
None,
suspended = false,
None
)
val opCall = IR.Application.Prefix(
op,
List(leftCallArg, rightCallArg),
hasDefaultsSuspended = false,
loc,
passData,
diagnostics
)
val rightLam = IR.Function.Lambda(
List(rightDefArg),
opCall,
None
)
IR.Function.Lambda(
List(leftDefArg),
rightLam,
None
)
} else {
val opCall = IR.Application.Prefix(
op,
List(arg, rightCallArg),
hasDefaultsSuspended = false,
loc,
passData,
diagnostics
)
IR.Function.Lambda(
List(rightDefArg),
opCall,
None
)
}
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),
leftArgName.duplicate(
keepLocations = false,
keepDiagnostics = false,
keepMetadata = false
),
None,
suspended = false,
None
)
val rightArgName = freshNameSupply.newName()
val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None)
val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.duplicate(
keepLocations = false,
keepDiagnostics = false,
keepMetadata = false
),
None,
suspended = false,
None
@ -116,23 +188,26 @@ case object SectionsToBinOp extends IRPass {
val opCall = IR.Application.Prefix(
op,
List(leftCallArg),
List(leftCallArg, rightCallArg),
hasDefaultsSuspended = false,
loc,
passData,
diagnostics
)
IR.Function.Lambda(
List(leftDefArg),
val rightLambda = IR.Function.Lambda(
List(rightDefArg),
opCall,
loc,
canBeTCO = true,
passData,
diagnostics
None
)
/* Note [Blanks in Right Sections]
IR.Function.Lambda(
List(leftDefArg),
rightLambda,
None
)
/* Note [Blanks in Sections]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* While the naiive compositional translation of `(- _)` first translates
* the section into a function applying `-` to two arguments, one of which
@ -147,6 +222,8 @@ case object SectionsToBinOp extends IRPass {
* `(- _)` == `x -> (- x)` == `x -> y -> y - x`
*
* We implement this special case here.
*
* The same is true of left sections.
*/
case Section.Right(op, arg, loc, passData, diagnostics) =>
@ -155,20 +232,27 @@ case object SectionsToBinOp extends IRPass {
IR.CallArgument.Specified(None, leftArgName, None, None)
val leftDefArg =
IR.DefinitionArgument.Specified(
// Ensure it has a different identifier
leftArgName.copy(id = IR.randomId),
leftArgName.duplicate(
keepLocations = false,
keepMetadata = false,
keepDiagnostics = false
),
None,
suspended = false,
None
)
if (arg.value.isInstanceOf[IR.Name.Blank]) {
// Note [Blanks in Right Sections]
// Note [Blanks in Sections]
val rightArgName = freshNameSupply.newName()
val rightCallArg =
IR.CallArgument.Specified(None, rightArgName, None, None)
val rightDefArg = IR.DefinitionArgument.Specified(
rightArgName.copy(id = IR.randomId),
rightArgName.duplicate(
keepLocations = false,
keepMetadata = false,
keepDiagnostics = false
),
None,
suspended = false,
None
@ -192,10 +276,7 @@ case object SectionsToBinOp extends IRPass {
IR.Function.Lambda(
List(rightDefArg),
leftLam,
loc,
canBeTCO = true,
passData,
diagnostics
None
)
} else {
val opCall = IR.Application.Prefix(
@ -210,10 +291,7 @@ case object SectionsToBinOp extends IRPass {
IR.Function.Lambda(
List(leftDefArg),
opCall,
loc,
canBeTCO = true,
passData,
diagnostics
None
)
}
}

View File

@ -168,8 +168,7 @@ case object LambdaConsolidate extends IRPass {
arguments = processedArgList,
body = runExpression(newBody, inlineContext),
location = newLocation,
canBeTCO = chainedLambdas.last.canBeTCO,
passData = MetadataStorage()
canBeTCO = chainedLambdas.last.canBeTCO
)
case _: IR.Function.Binding =>
throw new CompilerError(
@ -307,9 +306,10 @@ case object LambdaConsolidate extends IRPass {
case defSpec: IR.DefinitionArgument.Specified => defSpec.name.name
}
)
case ths: IR.Name.This => ths
case here: IR.Name.Here => here
case blank: IR.Name.Blank => blank
case ths: IR.Name.This => ths
case here: IR.Name.Here => here
case blank: IR.Name.Blank => blank
case ref: IR.Name.MethodReference => ref
}
} else {
name

View File

@ -105,6 +105,7 @@ case object DocumentationComments extends IRPass {
tpe.copy(body = resolveList(tpe.body).map(resolveIr))
case d: IR.Module.Scope.Definition.Atom => d
case doc: IR.Comment.Documentation => doc
case tySig: IR.Type.Ascription => tySig
case err: IR.Error => err
}

View File

@ -0,0 +1,265 @@
package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.core.IR.Type
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.desugar.ComplexType
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.compiler.pass.resolve.TypeSignatures.Signature
import scala.annotation.{nowarn, unused}
/** This pass is responsible for analysing type signatures to determine which
* arguments in a function definition are suspended.
*
* It searches for a correspondence between an argument position and the same
* position in the associated type signature, marking the argument as suspended
* if its type contains the _top-level_ constructor `Suspended`.
*
* It is a _best effort_ attempt for now, nothing more:
*
* - It only works on the syntactic structure of the signature.
* - It can only deal with a correspondence between a consolidated lambda and
* the signature, not with extended signatures that cover returned functions
* and the like.
*
* Additionally, we currently only support looking for `Suspended` alone, as
* supporting expressions like `Suspended a` will require the pattern contexts
* work.
*
* While the `~` syntax for suspension is still supported, the signature will
* take precedence over the `~` marking.
*
* This pass requires the context to provide:
*
* - Nothing
*/
case object SuspendedArguments extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = List(
ComplexType,
TypeSignatures,
LambdaConsolidate
)
override val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
UnusedBindings
)
/** Resolves suspended arguments in 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(resolveModuleBinding)
)
/** Resolves suspended arguments in 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 = resolveExpression(ir)
// === Pass Internals =======================================================
/** Resolves suspended arguments for a module binding.
*
* It is expected that module-level type signatures _do not_ include the
* `this` argument.
*
* @param binding the top-level binding to resolve suspensions in
* @return `binding`, with any suspended arguments resolved
*/
def resolveModuleBinding(
binding: IR.Module.Scope.Definition
): IR.Module.Scope.Definition = {
binding match {
case explicit @ Method.Explicit(_, body, _, _, _) =>
body match {
case lam @ IR.Function.Lambda(args, lamBody, _, _, _, _) =>
explicit.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
val newArgs = computeSuspensions(
args.drop(1),
signature
)
explicit.copy(body =
lam.copy(
arguments = args.head :: newArgs,
body = resolveExpression(lamBody)
)
)
case None =>
explicit.copy(
body = lam.copy(body = resolveExpression(lamBody))
)
}
case _ =>
throw new CompilerError(
"Method bodies must be lambdas at this point."
)
}
case _: Method.Binding => throw new CompilerError("")
case atom: Definition.Atom => atom
case err: IR.Error => err
case _: Definition.Type =>
throw new CompilerError(
"Complex type definitions should not be present."
)
case _: Type.Ascription =>
throw new CompilerError("Type ascriptions should not be present.")
case _: IR.Comment =>
throw new CompilerError("Comments should not be present.")
}
}
/** Resolves suspended arguments in an arbitrary expression.
*
* @param expression the expression to perform resolution in
* @return `expression`, with any suspended arguments resolved
*/
def resolveExpression(expression: IR.Expression): IR.Expression = {
expression.transformExpressions {
case bind @ IR.Expression.Binding(_, expr, _, _, _) =>
val newExpr = bind.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
expr match {
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
lam.copy(
arguments = computeSuspensions(args, signature),
body = resolveExpression(body)
)
case _ => expr
}
case None => expr
}
bind.copy(expression = newExpr)
case lam @ IR.Function.Lambda(args, body, _, _, _, _) =>
lam.getMetadata(TypeSignatures) match {
case Some(Signature(signature)) =>
lam.copy(
arguments = computeSuspensions(args, signature),
body = resolveExpression(body)
)
case None => lam.copy(body = resolveExpression(body))
}
}
}
/** Converts a type signature into segments.
*
* Segments are defined as the portions between the top-level lambdas in the
* type signature.
*
* @param signature the type signature to split
* @return the segments of `signature`
*/
def toSegments(signature: IR.Expression): List[IR.Expression] = {
signature match {
case IR.Application.Operator.Binary(
l,
IR.Name.Literal("->", _, _, _),
r,
_,
_,
_
) =>
l.value :: toSegments(r.value)
case IR.Function.Lambda(args, body, _, _, _, _) =>
args.map(_.name) ::: toSegments(body)
case _ => List(signature)
}
}
/** Checks if a value represents a suspended argument.
*
* @param value the value to check
* @return `true` if `value` represents a suspended argument, otherwise
* `false`
*/
def representsSuspended(value: IR.Expression): Boolean = {
value match {
case IR.Name.Literal("Suspended", _, _, _) => true
case _ => false
}
}
/** Marks an argument as suspended if it corresponds to a `Suspended` portion
* of the type signature.
*
* @param pair an argument and its corresponding type signature segment
* @return the argument from `pair`, with its suspension marked appropriately
*/
def markSuspended(
pair: (IR.DefinitionArgument, IR.Expression)
): IR.DefinitionArgument = pair match {
case (arg, typ) =>
arg match {
case spec: IR.DefinitionArgument.Specified =>
if (representsSuspended(typ)) {
spec.copy(suspended = true)
} else spec.copy(suspended = false)
}
}
/** Computes the suspensions for the arguments list of a function.
*
* @param args the function arguments
* @param signature the signature of the function
* @return `args`, appropriately marked as suspended or not
*/
def computeSuspensions(
args: List[IR.DefinitionArgument],
signature: IR.Expression
): List[IR.DefinitionArgument] = {
val signatureSegments = toSegments(signature)
val toComputeArgs =
if (args.length == signatureSegments.length) {
args.zip(signatureSegments)
} else if (args.length > signatureSegments.length) {
val additionalSegments = signatureSegments ::: List.fill(
signatureSegments.length - args.length
)(IR.Empty(None))
args.zip(additionalSegments)
} else {
args.zip(
signatureSegments
.dropRight(signatureSegments.length - args.length)
)
}
toComputeArgs.map(markSuspended)
}
}

View File

@ -0,0 +1,207 @@
package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Application
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.desugar.{
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
}
import org.enso.compiler.pass.lint.UnusedBindings
import scala.annotation.unused
/** This pass is responsible for lifting applications of type functions such as
* `:` and `in` and `!` into their specific IR nodes.
*
* This pass requires the context to provide:
*
* - Nothing
*/
case object TypeFunctions extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = List(
IgnoredBindings,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
)
override val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
UnusedBindings
)
/** Performs typing function resolution 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.transformExpressions {
case a => resolveExpression(a)
}
/** Performs typing function resolution on an 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 = ir.transformExpressions {
case a => resolveExpression(a)
}
// === Pass Internals =======================================================
/** The names of the known typing functions. */
val knownTypingFunctions: Set[String] = Set(
IR.Type.Ascription.name,
IR.Type.Context.name,
IR.Type.Error.name,
IR.Type.Set.Concat.name,
IR.Type.Set.Subsumption.name,
IR.Type.Set.Equality.name,
IR.Type.Set.Union.name,
IR.Type.Set.Intersection.name,
IR.Type.Set.Subtraction.name
)
/** Performs resolution of typing functions in an arbitrary expression.
*
* @param expr the expression to perform resolution in
* @return `expr`, with any typing functions resolved
*/
def resolveExpression(expr: IR.Expression): IR.Expression = {
expr.transformExpressions {
case app: IR.Application => resolveApplication(app)
}
}
/** Performs resolution of typing functions in an application.
*
* @param app the application to perform resolution in
* @return `app`, with any typing functions resolved
*/
def resolveApplication(app: IR.Application): IR.Expression = {
app match {
case pre @ Application.Prefix(fn, arguments, _, _, _, _) =>
fn match {
case name: IR.Name if knownTypingFunctions.contains(name.name) =>
resolveKnownFunction(pre)
case _ =>
pre.copy(
function = resolveExpression(fn),
arguments = arguments.map(resolveCallArgument)
)
}
case force @ Application.Force(target, _, _, _) =>
force.copy(target = resolveExpression(target))
case seq @ Application.Literal.Sequence(items, _, _, _) =>
seq.copy(
items = items.map(resolveExpression)
)
case tSet @ Application.Literal.Typeset(expr, _, _, _) =>
tSet.copy(
expression = expr.map(resolveExpression)
)
case _: Application.Operator =>
throw new CompilerError(
"Operators should not be present during typing functions lifting."
)
}
}
/** Resolves a known typing function to its IR node.
*
* @param prefix the application to resolve
* @return the IR node representing `prefix`
*/
def resolveKnownFunction(prefix: IR.Application.Prefix): IR.Expression = {
val expectedNumArgs = 2
val lengthIsValid = prefix.arguments.length == expectedNumArgs
val argsAreValid = prefix.arguments.forall(isValidCallArg)
if (lengthIsValid && argsAreValid) {
val leftArg = resolveExpression(prefix.arguments.head.value)
val rightArg = resolveExpression(prefix.arguments.last.value)
prefix.function.asInstanceOf[IR.Name].name match {
case IR.Type.Ascription.name =>
IR.Type.Ascription(leftArg, rightArg, prefix.location)
case IR.Type.Context.name =>
IR.Type.Context(leftArg, rightArg, prefix.location)
case IR.Type.Error.name =>
IR.Type.Error(leftArg, rightArg, prefix.location)
case IR.Type.Set.Concat.name =>
IR.Type.Set.Concat(leftArg, rightArg, prefix.location)
case IR.Type.Set.Subsumption.name =>
IR.Type.Set.Subsumption(leftArg, rightArg, prefix.location)
case IR.Type.Set.Equality.name =>
IR.Type.Set.Equality(leftArg, rightArg, prefix.location)
case IR.Type.Set.Union.name =>
IR.Type.Set.Union(leftArg, rightArg, prefix.location)
case IR.Type.Set.Intersection.name =>
IR.Type.Set.Intersection(leftArg, rightArg, prefix.location)
case IR.Type.Set.Subtraction.name =>
IR.Type.Set.Subtraction(leftArg, rightArg, prefix.location)
}
} else {
IR.Error.InvalidIR(prefix)
}
}
/** Performs resolution of typing functions in a call argument.
*
* @param arg the argument to perform resolution in
* @return `arg`, with any call arguments resolved
*/
def resolveCallArgument(arg: IR.CallArgument): IR.CallArgument = {
arg match {
case spec @ IR.CallArgument.Specified(_, value, _, _, _, _) =>
spec.copy(
value = resolveExpression(value)
)
}
}
// === Utilities ============================================================
/** Checks if a call argument is valid for a typing expression.
*
* As all typing functions are _operators_ in the source, their arguments
* must:
*
* - Not have a name defined.
* - Have no suspension info or not be suspended
*
* @param arg the argument to check
* @return `true` if `arg` is valid, otherwise `false`
*/
def isValidCallArg(arg: IR.CallArgument): Boolean = {
arg match {
case IR.CallArgument.Specified(name, _, _, susp, _, _) =>
name.isEmpty && (susp.isEmpty || susp.get)
}
}
}

View File

@ -0,0 +1,209 @@
package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.lint.UnusedBindings
import scala.annotation.unused
/** This pass is responsible for resolving type signatures and associating
* them as metadata with the typed object.
*
* Please note that this pass currently does not support typed patterns (and
* hence doesn't support inline types in lambdas). This support will come later
* with the work on expanding pattern contexts.
*
* This pass requires the context to provide:
*
* - Nothing
*/
case object TypeSignatures extends IRPass {
override type Metadata = Signature
override type Config = IRPass.Configuration.Default
override val precursorPasses: Seq[IRPass] = List(
TypeFunctions
)
override val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
UnusedBindings
)
/** Resolves type signatures in 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 = resolveModule(ir)
/** Resolves type signatures in an 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 = resolveExpression(ir)
// === Pass Internals =======================================================
/** Resolves type signatures in a module.
*
* @param mod the module to resolve signatures in
* @return `mod`, with type signatures resolved
*/
def resolveModule(mod: IR.Module): IR.Module = {
var lastSignature: Option[IR.Type.Ascription] = None
val newBindings: List[IR.Module.Scope.Definition] = mod.bindings.flatMap {
case sig: IR.Type.Ascription =>
val res = lastSignature match {
case Some(oldSig) => Some(IR.Error.Unexpected.TypeSignature(oldSig))
case None => None
}
lastSignature = Some(sig)
res
case meth: IR.Module.Scope.Definition.Method =>
val newMethod = meth.mapExpressions(resolveExpression)
val res = lastSignature match {
case Some(asc @ IR.Type.Ascription(typed, sig, _, _, _)) =>
val methodRef = meth.methodReference
typed match {
case ref: IR.Name.MethodReference =>
if (ref isSameReferenceAs methodRef) {
Some(newMethod.updateMetadata(this -->> Signature(sig)))
} else {
List(IR.Error.Unexpected.TypeSignature(asc), newMethod)
}
case _ =>
List(IR.Error.Unexpected.TypeSignature(asc), newMethod)
}
case None => Some(newMethod)
}
lastSignature = None
res
case atom: IR.Module.Scope.Definition.Atom =>
Some(atom.mapExpressions(resolveExpression))
case err: IR.Error => Some(err)
case _: IR.Module.Scope.Definition.Type =>
throw new CompilerError(
"Complex type definitions should not be present during type " +
"signature resolution."
)
case _: IR.Comment.Documentation =>
throw new CompilerError(
"Documentation comments should not be present during type " +
"signature resolution."
)
} ::: lastSignature
.map(asc => IR.Error.Unexpected.TypeSignature(asc))
.toList
mod.copy(
bindings = newBindings
)
}
/** Resolves type signatures in an arbitrary expression.
*
* @param expr the expression to resolve signatures in
* @return `expr`, with any type signatures resolved
*/
def resolveExpression(expr: IR.Expression): IR.Expression = {
expr.transformExpressions {
case block: IR.Expression.Block => resolveBlock(block)
case sig: IR.Type.Ascription => resolveAscription(sig)
}
}
/** Resolves type signatures in an ascription.
*
* @param sig the signature to convert
* @return the typed expression in `sig`, with `signature` attached
*/
def resolveAscription(sig: IR.Type.Ascription): IR.Expression = {
val newTyped = sig.typed.mapExpressions(resolveExpression)
val newSig = sig.signature.mapExpressions(resolveExpression)
newTyped.updateMetadata(this -->> Signature(newSig))
}
/** Resolves type signatures in a block.
*
* @param block the block to resolve signatures in
* @return `block`, with any type signatures resolved
*/
def resolveBlock(block: IR.Expression.Block): IR.Expression.Block = {
var lastSignature: Option[IR.Type.Ascription] = None
val allBlockExpressions =
block.expressions :+ block.returnValue
val newExpressions = allBlockExpressions.flatMap {
case sig: IR.Type.Ascription =>
val res = lastSignature match {
case Some(oldSig) => Some(IR.Error.Unexpected.TypeSignature(oldSig))
case None => None
}
lastSignature = Some(sig)
res
case binding: IR.Expression.Binding =>
val newBinding = binding.mapExpressions(resolveExpression)
val res = lastSignature match {
case Some(asc @ IR.Type.Ascription(typed, sig, _, _, _)) =>
val name = binding.name
typed match {
case typedName: IR.Name =>
if (typedName.name == name.name) {
Some(newBinding.updateMetadata(this -->> Signature(sig)))
} else {
List(IR.Error.Unexpected.TypeSignature(asc), newBinding)
}
case _ =>
List(IR.Error.Unexpected.TypeSignature(asc), newBinding)
}
case None => Some(newBinding)
}
lastSignature = None
res
case a => Some(resolveExpression(a))
} ::: lastSignature.map(IR.Error.Unexpected.TypeSignature(_)).toList
block.copy(
expressions = newExpressions.init,
returnValue = newExpressions.last
)
}
// === Metadata =============================================================
/** A representation of a type signature.
*
* @param signature the expression for the type signature
*/
case class Signature(signature: IR.Expression) extends IRPass.Metadata {
override val metadataName: String = "TypeSignatures.Signature"
}
}

View File

@ -24,7 +24,7 @@ trait CompilerRunner {
*
* @return [[source]] as an AST
*/
def toAST: AST = {
def toAst: AST = {
val parser: Parser = Parser()
val unresolvedAST = parser.runWithIds(source)
@ -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)
}
}
@ -163,8 +163,11 @@ trait CompilerRunner {
*/
def asMethod: IR.Module.Scope.Definition.Method = {
IR.Module.Scope.Definition.Method.Explicit(
IR.Name.Literal("TestType", None),
IR.Name.Literal("testMethod", None),
IR.Name.MethodReference(
List(IR.Name.Literal("TestType", None)),
IR.Name.Literal("testMethod", None),
None
),
ir,
None
)

View File

@ -8,7 +8,13 @@ import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.lint.ShadowedPatternFields
import org.enso.compiler.pass.optimise.UnreachableMatchBranches
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings}
import org.enso.compiler.pass.resolve.{
DocumentationComments,
IgnoredBindings,
SuspendedArguments,
TypeFunctions,
TypeSignatures
}
class PassesTest extends CompilerTest {
@ -50,7 +56,9 @@ class PassesTest extends CompilerTest {
ShadowedPatternFields,
UnreachableMatchBranches,
NestedPatternMatch,
IgnoredBindings
IgnoredBindings,
TypeFunctions,
TypeSignatures
)
)
}

View File

@ -1,7 +1,9 @@
package org.enso.compiler.test.codegen
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Error.Syntax
import org.enso.compiler.test.CompilerTest
import org.enso.syntax.text.Debug
import scala.annotation.unused
@ -513,7 +515,7 @@ class AstToIrTest extends CompilerTest {
}
}
"Documentation comments" should {
"AST translation for documentation comments" should {
"work at the top level" in {
val ir =
"""
@ -545,4 +547,240 @@ class AstToIrTest extends CompilerTest {
.doc shouldEqual " Some docs for b"
}
}
"AST translation for type operators" should {
"support type ascription" in {
val ir =
"""
|a : Type
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support context ascription" in {
val ir =
"""
|a in IO
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support error ascription" in {
val ir =
"""
|IO ! FileNotFound
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the subsumption operator" in {
val ir =
"""
|IO.Read <: IO
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the equality operator" in {
val ir =
"""
|T ~ Q
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the concatenation operator" in {
val ir =
"""
|a ; b
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the union operator" in {
val ir =
"""
|A | B
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the intersection operator" in {
val ir =
"""
|A & B
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"support the minus operator" in {
val ir =
"""
|A \ B
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
}
"AST translation for typeset literals" should {
"work properly" in {
val ir =
"""
|{ x := 0 ; y := 0 }
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Literal.Typeset]
}
}
"AST translation for top-level type signatures" should {
"work for method signatures" in {
val ir =
"""
|MyAtom.foo : Number -> Number -> Number
|MyAtom.foo = a -> b -> a + b
|""".stripMargin.toIrModule.bindings.head
ir shouldBe an[IR.Type.Ascription]
val asc = ir.asInstanceOf[IR.Type.Ascription]
asc.typed shouldBe an[IR.Name.MethodReference]
}
"work for module-method signatures" in {
val ir =
"""
|foo : Number -> Number
|foo a = a
|""".stripMargin.toIrModule.bindings.head
ir shouldBe an[IR.Type.Ascription]
val asc = ir.asInstanceOf[IR.Type.Ascription]
asc.typed shouldBe an[IR.Name.MethodReference]
}
"work with sugared syntax" in {
val ir =
"""
|MyAtom.foo : Number -> Number -> Number
|MyAtom.foo a b = a + b
|""".stripMargin.toIrModule.bindings.head
ir shouldBe an[IR.Type.Ascription]
val asc = ir.asInstanceOf[IR.Type.Ascription]
asc.typed shouldBe an[IR.Name.MethodReference]
}
"result in a syntax error if not valid" in {
val ir =
"""
|a b : Number -> Number -> Number
|MyAtom.foo a b = a + b
|""".stripMargin.toIrModule.bindings.head
ir shouldBe an[IR.Error.Syntax]
ir.asInstanceOf[IR.Error.Syntax]
.reason shouldBe an[Syntax.InvalidStandaloneSignature.type]
}
"work inside type bodies" in {
val ir =
"""
|type MyType
| type MyAtom
|
| foo : this -> integer
| foo = 0
|""".stripMargin.toIrModule.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Type]
ir.body.length shouldEqual 3
ir.body(1) shouldBe an[IR.Type.Ascription]
}
}
"AST translation for expression-level type signatures" should {
"work in block contexts" in {
val ir =
"""
|x : Number
|x = 10
|""".stripMargin.toIrExpression.get.asInstanceOf[IR.Expression.Block]
ir.expressions.head shouldBe an[IR.Application.Operator.Binary]
}
"work in expression contexts" in {
val ir =
"""
|(a + b) : Number
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Application.Operator.Binary]
}
"work properly when used in assignments" in {
val ir =
"""
|x = a : Number
|""".stripMargin.toIrExpression.get
ir shouldBe an[IR.Expression.Binding]
ir.asInstanceOf[IR.Expression.Binding]
.expression shouldBe an[IR.Application.Operator.Binary]
}
"properly support nested ascriptions" in {
val ir =
"""
|x : (a : Type) -> (b : Type -> Type) -> (c : Type)
|""".stripMargin.toIrExpression.get
.asInstanceOf[IR.Application.Operator.Binary]
ir.right.value
.asInstanceOf[IR.Application.Operator.Binary]
.left
.value shouldBe an[IR.Application.Operator.Binary]
}
"properly support the `in` context ascription operator" in {
val ir =
"""
|x : Number in (Maybe | IO)
|""".stripMargin.toIrExpression.get
.asInstanceOf[IR.Application.Operator.Binary]
ir.right.value shouldBe an[IR.Application.Operator.Binary]
ir.right.value
.asInstanceOf[IR.Application.Operator.Binary]
.operator
.name shouldEqual "in"
}
"properly support the `!` error ascription operator" in {
val ir =
"""
|x : Number in IO ! OverflowError
|""".stripMargin.toIrExpression.get
.asInstanceOf[IR.Application.Operator.Binary]
.right
.value
ir shouldBe an[IR.Application.Operator.Binary]
ir.asInstanceOf[IR.Application.Operator.Binary]
.operator
.name shouldEqual "!"
}
}
}

View File

@ -964,6 +964,33 @@ class AliasAnalysisTest extends CompilerTest {
}
}
"Alias analysis on typeset literals" should {
implicit val ctx: ModuleContext = mkModuleContext
val method =
"""
|main =
| { x := 1, b := 2 }
|""".stripMargin.preprocessModule.analyse.bindings.head
.asInstanceOf[Method]
val block = method.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
val blockScope =
block.unsafeGetMetadata(AliasAnalysis, "").unsafeAs[Info.Scope.Child]
val literal = block.returnValue.asInstanceOf[IR.Application.Literal.Typeset]
val literalScope =
literal.unsafeGetMetadata(AliasAnalysis, "").unsafeAs[Info.Scope.Child]
"create a new scope for the literal" in {
blockScope.scope.childScopes should contain(literalScope.scope)
}
}
"Redefinitions" should {
"be caught for bindings" in {
implicit val ctx: ModuleContext = mkModuleContext

View File

@ -873,6 +873,25 @@ class DataflowAnalysisTest extends CompilerTest {
depInfo.getDirect(vecId) shouldEqual Some(Set(lamId))
}
"work properly for typeset literals" in {
implicit val inlineContext: InlineContext = mkInlineContext
val ir =
"""
|{ x := a ; y := b }
|""".stripMargin.preprocessExpression.get.analyse
val depInfo = ir.getMetadata(DataflowAnalysis).get
val literal = ir.asInstanceOf[IR.Application.Literal.Typeset]
val literalExpression = literal.expression.get
val literalId = mkStaticDep(literal.getId)
val literalExpressionId = mkStaticDep(literalExpression.getId)
depInfo.getDirect(literalExpressionId).get shouldEqual Set(literalId)
}
"work properly for case expressions" in {
implicit val inlineContext: InlineContext = mkInlineContext
@ -891,9 +910,9 @@ class DataflowAnalysisTest extends CompilerTest {
val caseBindingExpr =
caseBinding.expression.asInstanceOf[IR.Application.Prefix]
val caseBindingName = caseBinding.name.asInstanceOf[IR.Name.Literal]
val caseExpr = caseBlock.returnValue.asInstanceOf[IR.Case.Expr]
val scrutinee = caseExpr.scrutinee.asInstanceOf[IR.Name.Literal]
val consBranch = caseExpr.branches.head
val caseExpr = caseBlock.returnValue.asInstanceOf[IR.Case.Expr]
val scrutinee = caseExpr.scrutinee.asInstanceOf[IR.Name.Literal]
val consBranch = caseExpr.branches.head
val consBranchPattern =
consBranch.pattern.asInstanceOf[Pattern.Constructor]

View File

@ -62,6 +62,9 @@ class GatherDiagnosticsTest extends CompilerTest {
val method2Name = IR.Name.Literal("baz", None)
val fooName = IR.Name.Literal("foo", None)
val method1Ref = IR.Name.MethodReference(List(typeName), method1Name, None)
val method2Ref = IR.Name.MethodReference(List(typeName), method2Name, None)
val module = IR.Module(
List(),
List(
@ -74,9 +77,9 @@ class GatherDiagnosticsTest extends CompilerTest {
None
),
IR.Module.Scope.Definition.Method
.Explicit(typeName, method1Name, lam, None),
.Explicit(method1Ref, lam, None),
IR.Module.Scope.Definition.Method
.Explicit(typeName, method2Name, error3, None)
.Explicit(method2Ref, error3, None)
),
None
)

View File

@ -59,11 +59,17 @@ class ComplexTypeTest extends CompilerTest {
| Nothing
| type Just a
|
| invalid_sig : Int
|
| is_just : this -> Boolean
| is_just = case this of
| Nothing -> false
| Just _ -> true
|
| f : (a: Monoid) -> a
| f a = this + a
|
| bad_trailing_sig : Double
|""".stripMargin.preprocessModule.desugar
"have their atoms desugared to top-level atoms" in {
@ -80,27 +86,110 @@ class ComplexTypeTest extends CompilerTest {
}
"have their methods desugared to methods on included atoms" in {
ir.bindings(1) shouldBe an[Definition.Method.Binding]
val justIsJust = ir.bindings(1).asInstanceOf[Definition.Method.Binding]
ir.bindings(3) shouldBe an[Definition.Method.Binding]
val justIsJust = ir.bindings(3).asInstanceOf[Definition.Method.Binding]
justIsJust.methodName.name shouldEqual "is_just"
justIsJust.typeName.name shouldEqual "Nothing"
ir.bindings(3) shouldBe an[Definition.Method.Binding]
val justF = ir.bindings(3).asInstanceOf[Definition.Method.Binding]
ir.bindings(7) shouldBe an[Definition.Method.Binding]
val justF = ir.bindings(7).asInstanceOf[Definition.Method.Binding]
justF.methodName.name shouldEqual "f"
justF.typeName.name shouldEqual "Nothing"
}
"have their methods desugared to methods on the defined atoms" in {
ir.bindings(2) shouldBe an[Definition.Method.Binding]
val justIsJust = ir.bindings(2).asInstanceOf[Definition.Method.Binding]
ir.bindings(5) shouldBe an[Definition.Method.Binding]
val justIsJust = ir.bindings(5).asInstanceOf[Definition.Method.Binding]
justIsJust.methodName.name shouldEqual "is_just"
justIsJust.typeName.name shouldEqual "Just"
ir.bindings(4) shouldBe an[Definition.Method.Binding]
val justF = ir.bindings(4).asInstanceOf[Definition.Method.Binding]
ir.bindings(9) shouldBe an[Definition.Method.Binding]
val justF = ir.bindings(9).asInstanceOf[Definition.Method.Binding]
justF.methodName.name shouldEqual "f"
justF.typeName.name shouldEqual "Just"
}
"have type signatures copied to above each method" in {
ir.bindings(2) shouldBe an[IR.Type.Ascription]
ir.bindings(6) shouldBe an[IR.Type.Ascription]
ir.bindings(4) shouldBe an[IR.Type.Ascription]
ir.bindings(8) shouldBe an[IR.Type.Ascription]
val nothingIsJustSigName = ir
.bindings(2)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.MethodReference]
val nothingIsJustMethodName = ir
.bindings(3)
.asInstanceOf[Definition.Method.Binding]
.methodReference
assert(
nothingIsJustSigName isSameReferenceAs nothingIsJustMethodName,
"The type signature and method did not have the same reference."
)
val nothingFSigName = ir
.bindings(6)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.MethodReference]
val nothingFMethodName = ir
.bindings(7)
.asInstanceOf[Definition.Method.Binding]
.methodReference
assert(
nothingFSigName isSameReferenceAs nothingFMethodName,
"The type signature and method did not have the same reference."
)
val justIsJustSigName = ir
.bindings(4)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.MethodReference]
val justIsJustMethodName = ir
.bindings(5)
.asInstanceOf[Definition.Method.Binding]
.methodReference
assert(
justIsJustSigName isSameReferenceAs justIsJustMethodName,
"The type signature and method did not have the same reference."
)
val justFSigName = ir
.bindings(8)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.MethodReference]
val justFMethodName = ir
.bindings(9)
.asInstanceOf[Definition.Method.Binding]
.methodReference
assert(
justFSigName isSameReferenceAs justFMethodName,
"The type signature and method did not have the same reference."
)
}
"leave un-associated signatures intact" in {
ir.bindings(1) shouldBe an[IR.Type.Ascription]
ir.bindings(1)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.Literal]
.name shouldEqual "invalid_sig"
ir.bindings(10) shouldBe an[IR.Type.Ascription]
ir.bindings(10)
.asInstanceOf[IR.Type.Ascription]
.typed
.asInstanceOf[IR.Name.Literal]
.name shouldEqual "bad_trailing_sig"
}
}
}

View File

@ -225,15 +225,21 @@ class LambdaShorthandToLambdaTest extends CompilerTest {
|""".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]
val underscoreFn = ir.asInstanceOf[IR.Function.Lambda]
val underscoreArgName = underscoreFn.arguments.head.name
body.arguments.length shouldEqual 1
val rightArgLambda = underscoreFn.body.asInstanceOf[IR.Function.Lambda]
rightArgLambda.arguments.length shouldEqual 1
val rightArgLambdaArgName = rightArgLambda.arguments.head.name
val leftArgName = body.arguments.head.value.asInstanceOf[IR.Name.Literal]
val body = rightArgLambda.body.asInstanceOf[IR.Application.Prefix]
body.arguments.length shouldEqual 2
argName.name shouldEqual leftArgName.name
val leftCallArgName = body.arguments.head.value.asInstanceOf[IR.Name.Literal]
val rightCallArgName = body.arguments(1).value.asInstanceOf[IR.Name.Literal]
underscoreArgName.name shouldEqual leftCallArgName.name
rightArgLambdaArgName.name shouldEqual rightCallArgName.name
}
"work correctly for centre operator sections" in {

View File

@ -56,8 +56,25 @@ class SectionsToBinOpTest extends CompilerTest {
|(1 +)
|""".stripMargin.preprocessExpression.get.desugar
ir shouldBe an[IR.Application.Prefix]
ir.asInstanceOf[IR.Application.Prefix].arguments.length shouldEqual 1
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(1)
.asInstanceOf[IR.CallArgument.Specified]
.value
.asInstanceOf[IR.Name.Literal]
lamBodyFirstArg.name shouldEqual lamArgName.name
lamBodyFirstArg.getId should not equal lamArgName.getId
}
"work for sides sections" in {
@ -70,22 +87,34 @@ class SectionsToBinOpTest extends CompilerTest {
ir shouldBe an[IR.Function.Lambda]
val irLam = ir.asInstanceOf[IR.Function.Lambda]
irLam.arguments.length shouldEqual 1
val leftLam = ir.asInstanceOf[IR.Function.Lambda]
leftLam.arguments.length shouldEqual 1
val leftLamArgName =
leftLam.arguments.head
.asInstanceOf[IR.DefinitionArgument.Specified]
.name
val lamArgName =
irLam.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified].name
val rightLam = leftLam.body.asInstanceOf[IR.Function.Lambda]
rightLam.arguments.length shouldEqual 1
val rightLamArgName = rightLam.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]
val lamBody = rightLam.body.asInstanceOf[IR.Application.Prefix]
lamBody.arguments.length shouldEqual 2
val lamBodyFirstArg = lamBody.arguments.head
.asInstanceOf[IR.CallArgument.Specified]
.value
.asInstanceOf[IR.Name.Literal]
val lamBodySecondArg = lamBody.arguments(1)
.asInstanceOf[IR.CallArgument.Specified]
.value
.asInstanceOf[IR.Name.Literal]
lamBodyFirstArg.name shouldEqual lamArgName.name
lamBodyFirstArg.getId should not equal lamArgName.getId
lamBodyFirstArg.name shouldEqual leftLamArgName.name
lamBodySecondArg.name shouldEqual rightLamArgName.name
lamBodyFirstArg.getId should not equal leftLamArgName.getId
lamBodySecondArg.getId should not equal rightLamArgName.getId
}
"work for right sections" in {
@ -125,8 +154,15 @@ class SectionsToBinOpTest extends CompilerTest {
|""".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
ir.body
.asInstanceOf[IR.Function.Lambda]
.body shouldBe an[IR.Application.Prefix]
ir.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Application.Prefix]
.arguments
.length shouldEqual 2
}
"flip the arguments when a right section's argument is a blank" in {

View File

@ -26,7 +26,7 @@ class IgnoredBindingsTest extends CompilerTest {
*
* @param ir the IR to desugar
*/
implicit class DesugarExpression(ir: IR.Expression) {
implicit class ResolveExpression(ir: IR.Expression) {
/** Runs ignores desugaring on [[ir]].
*
@ -34,7 +34,7 @@ class IgnoredBindingsTest extends CompilerTest {
* place
* @return [[ir]], with all ignores desugared
*/
def desugar(implicit inlineContext: InlineContext): IR.Expression = {
def resolve(implicit inlineContext: InlineContext): IR.Expression = {
IgnoredBindings.runExpression(ir, inlineContext)
}
}
@ -55,7 +55,7 @@ class IgnoredBindingsTest extends CompilerTest {
val ir =
"""
|_ -> (x = _ -> 1) -> x
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin.preprocessExpression.get.resolve
.asInstanceOf[IR.Function.Lambda]
val blankArg =
ir.arguments.head.asInstanceOf[IR.DefinitionArgument.Specified]
@ -98,7 +98,7 @@ class IgnoredBindingsTest extends CompilerTest {
| _ = f a b
| x = y
| 10
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin.preprocessExpression.get.resolve
.asInstanceOf[IR.Expression.Binding]
val bindingName = ir.name
@ -137,7 +137,7 @@ class IgnoredBindingsTest extends CompilerTest {
|case x of
| Cons a _ -> case y of
| MyCons a _ -> 10
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin.preprocessExpression.get.resolve
.asInstanceOf[IR.Expression.Block]
.returnValue
.asInstanceOf[IR.Case.Expr]

View File

@ -0,0 +1,196 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Definition.Method
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.resolve.SuspendedArguments
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.runtime.scope.LocalScope
import org.enso.compiler.pass.PassConfiguration._
class SuspendedArgumentsTest extends CompilerTest {
// === Test Setup ===========================================================
val passes = new Passes
val precursorPasses: List[IRPass] =
passes.getPrecursors(SuspendedArguments).get
val passConfiguration: PassConfiguration = PassConfiguration(
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =
new PassManager(precursorPasses, passConfiguration)
/** Adds an extension method to a module for performing suspended argument
* resolution.
*
* @param ir the IR to add the extension method to
*/
implicit class ResolveModule(ir: IR.Module) {
/** Resolves suspended arguments in [[ir]].
*
* @param moduleContext the context in which resolution is taking place
* @return [[ir]], with all suspended arguments resolved
*/
def resolve(implicit moduleContext: ModuleContext): IR.Module = {
SuspendedArguments.runModule(ir, moduleContext)
}
}
/** Adds an extension method to an expression for performing suspended
* argument resolution.
*
* @param ir the expression to add the extension method to
*/
implicit class ResolveExpression(ir: IR.Expression) {
/** Resolves suspended arguments in [[ir]].
*
* @param inlineContext the context in which resolution is taking place
* @return [[ir]], with all suspended arguments resolved
*/
def resolve(implicit inlineContext: InlineContext): IR.Expression = {
SuspendedArguments.runExpression(ir, inlineContext)
}
}
/** Creates a defaulted module context.
*
* @return a defaulted module context
*/
def mkModuleContext: ModuleContext = {
ModuleContext(freshNameSupply = Some(new FreshNameSupply))
}
/** Creates a defaulted inline context.
*
* @return a defaulted inline context
*/
def mkInlineContext: InlineContext = {
InlineContext(
freshNameSupply = Some(new FreshNameSupply),
localScope = Some(LocalScope.root)
)
}
// === The Tests ============================================================
"Suspended arguments resolution in modules" should {
"correctly mark arguments as suspended based on their type signatures" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|Any.id : Suspended -> a
|Any.id a = a
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[Method]
val bodyLam = ir.body.asInstanceOf[IR.Function.Lambda]
bodyLam.arguments.length shouldEqual 2
assert(
!bodyLam.arguments.head.suspended,
"the `this` argument is suspended"
)
assert(
bodyLam.arguments(1).suspended,
"the `a` argument is not suspended"
)
}
"work recursively" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|Any.id : Suspended -> a
|Any.id a =
| lazy_id : Suspended -> a
| lazy_id x = x
|
| lazy_id a
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[Method]
val bodyBlock = ir.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
val lazyId = bodyBlock.expressions.head
.asInstanceOf[IR.Expression.Binding]
.expression
.asInstanceOf[IR.Function.Lambda]
assert(lazyId.arguments.head.suspended, "x was not suspended")
}
}
"Suspended arguments resolution in expressions" should {
"correctly mark arguments as suspended in blocks" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|f : a -> Suspended -> b
|f a b = b
|""".stripMargin.preprocessExpression.get.resolve
.asInstanceOf[IR.Expression.Block]
val func = ir.returnValue
.asInstanceOf[IR.Expression.Binding]
.expression
.asInstanceOf[IR.Function.Lambda]
assert(!func.arguments.head.suspended, "a is suspended")
assert(func.arguments(1).suspended, "b is not suspended")
}
"correctly mark arguments as suspended using inline expressions" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|(x -> y -> y + x) : Suspended -> a -> a
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
val lam = ir.asInstanceOf[IR.Function.Lambda]
assert(lam.arguments.head.suspended, "x is not suspended")
assert(!lam.arguments(1).suspended, "y is suspended")
}
"work recursively" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|x -> y ->
| f : a -> Suspended -> a
| f a b = a + b
|
| f 100 50
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
val lam = ir.asInstanceOf[IR.Function.Lambda]
val f = lam.body
.asInstanceOf[IR.Expression.Block]
.expressions
.head
.asInstanceOf[IR.Expression.Binding]
.expression
.asInstanceOf[IR.Function.Lambda]
assert(!f.arguments.head.suspended, "a was suspended")
assert(f.arguments(1).suspended, "b was not suspended")
}
}
}

View File

@ -0,0 +1,199 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.resolve.{IgnoredBindings, TypeFunctions}
import org.enso.compiler.pass.{IRPass, PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
class TypeFunctionsTest extends CompilerTest {
// === Test Setup ===========================================================
val passes = new Passes
val precursorPasses: List[IRPass] = passes.getPrecursors(TypeFunctions).get
val passConfiguration: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(precursorPasses, passConfiguration)
/** Adds an extension method to resolve typing functions to an expression.
*
* @param ir the expression to resolve typing functions in
*/
implicit class ResolveExpression(ir: IR.Expression) {
/** Resolves typing functions on [[ir]].
*
* @param inlineContext the context win which resolution takes place
* @return [[ir]], with typing functions resolved
*/
def resolve(implicit inlineContext: InlineContext): IR.Expression = {
TypeFunctions.runExpression(ir, inlineContext)
}
}
/** Makes an inline context.
*
* @return a new inline context
*/
def mkInlineContext: InlineContext = {
InlineContext(freshNameSupply = Some(new FreshNameSupply))
}
// === The Tests ============================================================
"Type functions resolution" should {
implicit val ctx: InlineContext = mkInlineContext
"work for saturated applications" in {
val ir =
"""
|a : B
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Ascription]
}
"work for left sections" in {
val ir =
"""
|(a :)
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Type.Ascription]
}
"work for centre sections" in {
val ir =
"""
|(:)
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
ir.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Function.Lambda]
.body shouldBe an[IR.Type.Ascription]
}
"work for right sections" in {
val ir =
"""
|(: a)
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Type.Ascription]
}
"work for underscore arguments on the left" in {
val ir =
"""
|_ : A
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Type.Ascription]
}
"work for underscore arguments on the right" in {
val ir =
"""
|a : _
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Function.Lambda]
ir.asInstanceOf[IR.Function.Lambda].body shouldBe an[IR.Type.Ascription]
}
}
"Resolution" should {
implicit val ctx: InlineContext = mkInlineContext
"resolve type ascription" in {
val ir =
"""
|a : A
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Ascription]
}
"resolve context ascription" in {
val ir =
"""
|a in IO
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Context]
}
"resolve error ascription" in {
val ir =
"""
|IO ! Error
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Error]
}
"resolve subsumption" in {
val ir =
"""
|T <: P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Subsumption]
}
"resolve equality" in {
val ir =
"""
|T ~ P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Equality]
}
"resolve concatenation" in {
val ir =
"""
|T ; P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Concat]
}
"resolve union" in {
val ir =
"""
|T | P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Union]
}
"resolve intersection" in {
val ir =
"""
|T & P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Intersection]
}
"resolve subtraction" in {
val ir =
"""
|T \ P
|""".stripMargin.preprocessExpression.get.resolve
ir shouldBe an[IR.Type.Set.Subtraction]
}
}
}

View File

@ -0,0 +1,210 @@
package org.enso.compiler.test.pass.resolve
import org.enso.compiler.Passes
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.resolve.TypeSignatures
import org.enso.compiler.test.CompilerTest
class TypeSignaturesTest extends CompilerTest {
// === Test Setup ===========================================================
val passes = new Passes
val precursorPasses: List[IRPass] = passes.getPrecursors(TypeSignatures).get
val passConfiguration: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(precursorPasses, passConfiguration)
/** Adds an extension method to a module for performing type signature
* resolution.
*
* @param ir the IR to add the extension method to
*/
implicit class ResolveModule(ir: IR.Module) {
/** Resolves type signatures in [[ir]].
*
* @param moduleContext the context in which resolution is taking place
* @return [[ir]], with all type signatures resolved
*/
def resolve(implicit moduleContext: ModuleContext): IR.Module = {
TypeSignatures.runModule(ir, moduleContext)
}
}
/** Adds an extension method to an expression for performing type signature
* resolution.
*
* @param ir the expression to add the extension method to
*/
implicit class ResolveExpression(ir: IR.Expression) {
/** Resolves type signatures in [[ir]].
*
* @param inlineContext the context in which resolution is taking place
* @return [[ir]], with all type signatures resolved
*/
def resolve(implicit inlineContext: InlineContext): IR.Expression = {
TypeSignatures.runExpression(ir, inlineContext)
}
}
/** Creates a defaulted module context.
*
* @return a defaulted module context
*/
def mkModuleContext: ModuleContext = {
ModuleContext(freshNameSupply = Some(new FreshNameSupply))
}
/** Creates a defaulted inline context.
*
* @return a defaulted inline context
*/
def mkInlineContext: InlineContext = {
InlineContext(freshNameSupply = Some(new FreshNameSupply))
}
// === The Tests ============================================================
"Resolution of type signatures in modules" should {
implicit val ctx: ModuleContext = mkModuleContext
"associate signatures with method definitions" in {
val ir =
"""
|MyAtom.bar : Number -> Suspended Number -> Number
|MyAtom.bar a b = a + b
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head.getMetadata(TypeSignatures) shouldBe defined
}
"raise an error if a signature is divorced from its definition" in {
val ir =
"""
|MyAtom.quux : Fizz
|MyAtom.foo : Number -> Number -> Number
|
|MyAtom.bar = 1
|
|MyAtom.foo a b = a + b
|
|MyAtom.baz : Int
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 5
ir.bindings.head shouldBe an[IR.Error.Unexpected.TypeSignature]
ir.bindings(1) shouldBe an[IR.Error.Unexpected.TypeSignature]
ir.bindings(2) shouldBe an[IR.Module.Scope.Definition.Method]
ir.bindings(3) shouldBe an[IR.Module.Scope.Definition.Method]
ir.bindings(4) shouldBe an[IR.Error.Unexpected.TypeSignature]
}
"work inside type definition bodies" in {
val ir =
"""
|type MyType
| type MyAtom
|
| is_atom : this -> Boolean
| is_atom = true
|
| error_signature : Int
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 3
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Atom]
ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Method]
ir.bindings(1).getMetadata(TypeSignatures) shouldBe defined
ir.bindings(2) shouldBe an[IR.Error.Unexpected.TypeSignature]
}
"recurse into bodies" in {
val ir =
"""
|main =
| f : a -> a
| f a = a
|
| f 1
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method]
val block = ir.body
.asInstanceOf[IR.Function.Lambda]
.body
.asInstanceOf[IR.Expression.Block]
block.expressions.length shouldEqual 1
block.expressions.head.getMetadata(TypeSignatures) shouldBe defined
}
}
"Resolution of type signatures for blocks" should {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|block =
| f : Int
| f = 0
|
| g : Int
| g =
| f : Double
| f = 0
| f 1
|
| bad_sig : Int
|""".stripMargin.preprocessExpression.get.resolve
.asInstanceOf[IR.Expression.Binding]
val block = ir.expression.asInstanceOf[IR.Expression.Block]
"associate signatures with bindings" in {
block.expressions.head shouldBe an[IR.Expression.Binding]
block.expressions.head.getMetadata(TypeSignatures) shouldBe defined
}
"raise an error if a signature is divorced from its definition" in {
block.returnValue shouldBe an[IR.Error.Unexpected.TypeSignature]
}
"work recursively" in {
val nested = block
.expressions(1)
.asInstanceOf[IR.Expression.Binding]
.expression
.asInstanceOf[IR.Expression.Block]
nested.expressions.head shouldBe an[IR.Expression.Binding]
nested.expressions.head.getMetadata(TypeSignatures) shouldBe defined
}
}
"Resolution of inline type signatures" should {
implicit val ctx: InlineContext = mkInlineContext
val ir =
"""
|f a (b = 1 : Int) : Double
|""".stripMargin.preprocessExpression.get.resolve
"associate the signature with the typed expression" in {
ir shouldBe an[IR.Application.Prefix]
ir.getMetadata(TypeSignatures) shouldBe defined
}
"work recursively" in {
val arg2Value = ir.asInstanceOf[IR.Application.Prefix].arguments(1).value
arg2Value shouldBe an[IR.Literal.Number]
}
}
}

View File

@ -9,7 +9,9 @@ class SuspendedArgumentsTest extends InterpreterTest {
val code =
"""
|main =
| lazyId = ~x -> x
| lazyId : Suspended -> a
| lazyId = x -> x
|
| lazyId (1 + 1)
|""".stripMargin
@ -20,7 +22,9 @@ class SuspendedArgumentsTest extends InterpreterTest {
val code =
"""
|main =
| foo = i -> ~x -> ~y -> ifZero i x y
| foo : Number -> Suspended -> Suspended -> a
| foo = i -> x -> y -> ifZero i x y
|
| foo 1 (IO.println 1) (IO.println 2)
|""".stripMargin
eval(code)
@ -42,7 +46,9 @@ class SuspendedArgumentsTest extends InterpreterTest {
val code =
"""
|main =
| suspInc = ~x -> 1 + x
| suspInc : Suspended -> Number
| suspInc = x -> 1 + x
|
| suspInc (suspInc 10)
|""".stripMargin

View File

@ -345,6 +345,8 @@ object Shape extends ShapeImplicit {
extends SpacelessAST[T]
final case class Group[T](body: Option[T]) extends SpacelessAST[T]
final case class SequenceLiteral[T](items: List[T]) extends SpacelessAST[T]
final case class TypesetLiteral[T](expression: Option[T])
extends SpacelessAST[T]
final case class Def[T](name: AST.Cons, args: List[T], body: Option[T])
extends SpacelessAST[T]
final case class Foreign[T](indent: Int, lang: String, code: List[String])
@ -1009,6 +1011,15 @@ object Shape extends ShapeImplicit {
}
object TypesetLiteral {
implicit def ftor: Functor[TypesetLiteral] = semi.functor
implicit def fold: Foldable[Def] = semi.foldable
implicit def repr[T: Repr]: Repr[TypesetLiteral[T]] =
t => s"{ ${t.expression.repr.build()} }"
implicit def ozip[T]: OffsetZip[TypesetLiteral, T] = _.map(Index.Start -> _)
implicit def span[T]: HasSpan[TypesetLiteral[T]] = _ => 0
}
object Def {
implicit def ftor: Functor[Def] = semi.functor
implicit def fold: Foldable[Def] = semi.foldable
@ -1103,6 +1114,7 @@ sealed trait ShapeImplicit {
case s: Mixfix[T] => s.repr
case s: Group[T] => s.repr
case s: SequenceLiteral[T] => s.repr
case s: TypesetLiteral[T] => s.repr
case s: Def[T] => s.repr
case s: Foreign[T] => s.repr
}
@ -1141,6 +1153,7 @@ sealed trait ShapeImplicit {
case s: Mixfix[T] => OffsetZip[Mixfix, T].zipWithOffset(s)
case s: Group[T] => OffsetZip[Group, T].zipWithOffset(s)
case s: SequenceLiteral[T] => OffsetZip[SequenceLiteral, T].zipWithOffset(s)
case s: TypesetLiteral[T] => OffsetZip[TypesetLiteral, T].zipWithOffset(s)
case s: Def[T] => OffsetZip[Def, T].zipWithOffset(s)
case s: Foreign[T] => OffsetZip[Foreign, T].zipWithOffset(s)
}
@ -1180,6 +1193,7 @@ sealed trait ShapeImplicit {
case s: Mixfix[T] => s.span()
case s: Group[T] => s.span()
case s: SequenceLiteral[T] => s.span()
case s: TypesetLiteral[T] => s.span()
case s: Def[T] => s.span()
case s: Foreign[T] => s.span()
}
@ -2333,7 +2347,7 @@ object AST {
}
//////////////////////////////////////////////////////////////////////////////
//// SequenceLiteral /////////////////////////////////////////////////////////////
//// SequenceLiteral /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
type SequenceLiteral = ASTOf[Shape.SequenceLiteral]
@ -2344,6 +2358,19 @@ object AST {
def apply(items: List[AST]): SequenceLiteral = Shape.SequenceLiteral(items)
}
//////////////////////////////////////////////////////////////////////////////
//// Typeset Literal /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
type TypesetLiteral = ASTOf[Shape.TypesetLiteral]
object TypesetLiteral {
val any = UnapplyByType[TypesetLiteral]
def unapply(t: AST): Option[Option[AST]] =
Unapply[TypesetLiteral].run(_.expression)(t)
def apply(expression: Option[AST]): TypesetLiteral =
Shape.TypesetLiteral(expression)
}
//////////////////////////////////////////////////////////////////////////////
//// Def /////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

View File

@ -49,6 +49,25 @@ object Builtin {
}
}
val typesetLiteral = {
val expression = Pattern.Expr(allowBlocks = false).opt
Definition(
Opr("{") -> expression,
Opr("}")
) { ctx =>
ctx.body match {
case List(seg1, _) =>
val body = seg1.body.toStream.map(_.wrapped)
body match {
case List(expr) =>
AST.TypesetLiteral(Some(expr))
case _ => AST.TypesetLiteral(None)
}
case _ => internalError
}
}
}
val defn = Definition(Var("type") -> {
val head = Pattern.Cons().or("missing name").tag("name")
val args =
@ -287,6 +306,7 @@ object Builtin {
Registry(
group,
sequenceLiteral,
typesetLiteral,
case_of,
if_then,
if_then_else,

View File

@ -22,6 +22,7 @@ object Assoc {
def of(op: String): Assoc =
if (isApplicative(op)) Assoc.Left
else if (op == "in") Assoc.Left
else if (op.foldLeft(0)(_ + charAssoc(_)) >= 0) Assoc.Left
else Assoc.Right
}

View File

@ -1,18 +1,26 @@
package org.enso.syntax.text.ast.opr
object Prec {
/** The precedence hierarchy, from loosest binding to tightest binding. */
val hierarchy = List(
List("=", "#="),
List(";"),
List(":="),
List(":"),
List("->", "<-"),
List("~>", "<~"),
List("!"),
List("in"),
List("<:", "~"),
List("|"),
List("&"),
List("!", "?", "~"),
List("\\"),
List("?"),
List("<*", "<*>", "*>", "<$", "<$>", "$>", "<+", "<+>", "+>"),
List("<", ">"),
List(":", ","),
List(","),
List("+", "-"),
List("*", "/", "\\", "%"),
List("*", "/", "%"),
List("^"),
List("."),
List(" ")

View File

@ -206,7 +206,7 @@ case class ParserDef() extends flexer.Parser[AST.Module] {
ident.current = Some(opr)
}
val char: Pattern = anyOf("!$%&*+-/<>?^~|:\\")
val char: Pattern = anyOf(";!$%&*+-/<>?^~|:\\")
val errChar: Pattern = char | "=" | "," | "."
val errSfx: Pattern = errChar.many1
val body: Pattern = char.many1

View File

@ -0,0 +1,49 @@
package org.enso.syntax.text
import cats.implicits._
import org.enso.syntax.text.AST.App
/** This preprocessor step is responsible for hoisting occurrences of `in` as a
* variable identifier to occurrences of an _operator_ identifier, letting it
* behave properly in the syntax.
*/
case object InHoisting {
private val inName: String = "in"
/** Executes the hoisting procedure on the provided token stream.
*
* @param tokenStream the input token stream
* @return `tokenStream` with all occurrences of `Var("in")` replaced with
* `Opr("in")`
*/
def run(tokenStream: AST.Module): AST.Module = {
tokenStream.setLines(
tokenStream.lines.map(l => l.copy(elem = l.elem.map(process)))
)
}
/** Maps over the token stream, replacing occurrences of `Var("in")` with
* `Opr("in")` at the same location.
*
* @param ast the ast element to process
* @return `ast`, with occurrences of "in" replaced
*/
def process(ast: AST): AST = ast match {
case App.Prefix.any(app) =>
App.Prefix(process(app.func), app.off, process(app.arg))
case AST.Ident.Var.any(v) =>
if (v.name == inName) {
v.copy(
shape = Shape.Opr("in")
)
} else {
v
}
case AST.Block.any(block) =>
val newFirstLine = block.firstLine.map(process)
val newLines = block.lines.map(_.map(_.map(process)))
block.replaceFirstLine(newFirstLine).replaceLines(newLines)
case n => n.map(process)
}
}

View File

@ -228,7 +228,8 @@ class Parser {
/** Parse input with provided IdMap into AST */
def run(input: Reader, idMap: IDMap): AST.Module = {
val tokenStream = engine.run(input)
val tokenStream = engine.run(input).map(InHoisting.run)
val spanned = tokenStream.map(attachModuleLocations)
val resolved = spanned.map(Macro.run) match {
case flexer.Parser.Result(_, flexer.Parser.Result.Success(mod)) =>