Skip to content

Commit b9d4108

Browse files
authored
Kindless on the Surface, but kind(ed) in the Core, symbolically: Better, recoverable error messages for wrong kinds (#947)
Resolves #667 (see there for context) Also see #949 which constituted the second part of this. Types in surface.Tree are now kindless, not distinguished by Value/BlockType (we keep these just for docs). This means that the parser for types is now exceedingly liberal, yet unkind(ed). The Namer then resolves surface.Tree.Type to symbol.Value/BlockType. The only changes there are explicit `resolveValue/BlockType` operators.
1 parent 0db183f commit b9d4108

18 files changed

+357
-202
lines changed

effekt/jvm/src/test/scala/effekt/RecursiveDescentTests.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ class RecursiveDescentTests extends munit.FunSuite {
260260
|""".stripMargin)
261261

262262
parseStmts("val (left, right) = list; return left")
263+
264+
parseStmts("val g: () => Unit / Exc at {exc} = fun() { closure() }; ()")
263265
}
264266

265267
test("Semicolon insertion") {
@@ -325,7 +327,7 @@ class RecursiveDescentTests extends munit.FunSuite {
325327
test("Value types") {
326328
assertEquals(
327329
parseValueType("Int"),
328-
ValueTypeRef(IdRef(Nil, "Int"), Nil))
330+
TypeRef(IdRef(Nil, "Int"), Nil))
329331

330332
parseValueType("List[Int]")
331333
parseValueType("list::List[Int]")
@@ -342,8 +344,8 @@ class RecursiveDescentTests extends munit.FunSuite {
342344

343345
assertEquals(
344346
parseBlockType("(Int, String) => Int"),
345-
FunctionType(Nil, List(ValueTypeRef(IdRef(Nil,"Int"), Nil),
346-
ValueTypeRef(IdRef(Nil,"String"), Nil)), Nil, ValueTypeRef(IdRef(Nil, "Int"), Nil), Effects(Nil)))
347+
FunctionType(Nil, List(TypeRef(IdRef(Nil,"Int"), Nil),
348+
TypeRef(IdRef(Nil,"String"), Nil)), Nil, TypeRef(IdRef(Nil, "Int"), Nil), Effects(Nil)))
347349

348350
parseBlockType("(Int, String) => Int / Exc")
349351
parseBlockType("[T](Int, String) => Int / { Exc, State[T] }")
@@ -356,6 +358,9 @@ class RecursiveDescentTests extends munit.FunSuite {
356358
parseBlockType("[T] => T") // Not sure we want this...
357359

358360
parseValueType("Exc at { a, b, c }")
361+
intercept[Throwable] { parseBlockType("Exc / Eff") }
362+
intercept[Throwable] { parseBlockType("Exc / {}") }
363+
359364
parseValueType("() => (Exc at {}) / {} at { a, b, c }")
360365

361366
assertEquals(
@@ -368,6 +373,8 @@ class RecursiveDescentTests extends munit.FunSuite {
368373
parseValueType("() => (Int at { a, b, c }) at {}")
369374
parseValueType("(() => Int) at { a, b, c }")
370375
parseValueType("(() => Int at {}) => Int at { a, b, c }")
376+
377+
parseValueType("() => Unit / Socket at {io, async, global}")
371378
}
372379

373380
test("Params") {
@@ -413,7 +420,7 @@ class RecursiveDescentTests extends munit.FunSuite {
413420
test("Implementations") {
414421
assertEquals(
415422
parseImplementation("Foo {}"),
416-
Implementation(BlockTypeRef(IdRef(Nil, "Foo"), Nil), Nil))
423+
Implementation(TypeRef(IdRef(Nil, "Foo"), Nil), Nil))
417424

418425
parseImplementation("Foo[T] {}")
419426
parseImplementation("Foo[T] { def bar() = 42 }")
@@ -431,7 +438,7 @@ class RecursiveDescentTests extends munit.FunSuite {
431438
assertEquals(
432439
parseImplementation("Foo { 43 }"),
433440
Implementation(
434-
BlockTypeRef(IdRef(Nil, "Foo"), Nil),
441+
TypeRef(IdRef(Nil, "Foo"), Nil),
435442
List(OpClause(IdRef(Nil, "Foo"), Nil, Nil, Nil, None,
436443
Return(Literal(43, symbols.builtins.TInt)), IdDef("resume")))))
437444
}
@@ -563,6 +570,11 @@ class RecursiveDescentTests extends munit.FunSuite {
563570
parseDefinition(
564571
"""def foo[T](x: Int): String / {} = e
565572
|""".stripMargin)
573+
574+
parseDefinition(
575+
"""def op(): Int => Int at {greeter} / Greet = f
576+
|""".stripMargin
577+
)
566578
}
567579

568580
test("Toplevel definitions") {

effekt/shared/src/main/scala/effekt/Namer.scala

Lines changed: 124 additions & 39 deletions
Large diffs are not rendered by default.

effekt/shared/src/main/scala/effekt/RecursiveDescent.scala

Lines changed: 95 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -702,11 +702,11 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
702702
def implementation(): Implementation =
703703
nonterminal:
704704
// Interface[...] {}
705-
def emptyImplementation() = backtrack { Implementation(interfaceType(), `{` ~> Nil <~ `}`) }
705+
def emptyImplementation() = backtrack { Implementation(blockTypeRef(), `{` ~> Nil <~ `}`) }
706706

707707
// Interface[...] { def <NAME> = ... }
708708
def interfaceImplementation() = backtrack {
709-
val tpe = interfaceType()
709+
val tpe = blockTypeRef()
710710
consume(`{`)
711711
if !peek(`def`) then fail("Expected at least one operation definition to implement this interface.")
712712
tpe
@@ -719,7 +719,7 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
719719
def operationImplementation() = idRef() ~ maybeTypeArgs() ~ implicitResume ~ functionArg() match {
720720
case (id ~ tps ~ k ~ BlockLiteral(_, vps, bps, body)) =>
721721
val synthesizedId = IdRef(Nil, id.name).withPositionOf(id)
722-
val interface = BlockTypeRef(id, tps).withPositionOf(id): BlockTypeRef
722+
val interface = TypeRef(id, tps).withPositionOf(id)
723723
val operation = OpClause(synthesizedId, Nil, vps, bps, None, body, k).withRangeOf(id, body)
724724
Implementation(interface, List(operation))
725725
}
@@ -866,9 +866,8 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
866866
case _ => sys.error(s"Internal compiler error: not a valid operator ${op}")
867867
}
868868

869-
private def TupleTypeTree(tps: List[ValueType]): ValueType =
870-
ValueTypeRef(IdRef(List("effekt"), s"Tuple${tps.size}"), tps)
871-
// TODO positions!
869+
def TypeTuple(tps: List[Type]): Type =
870+
TypeRef(IdRef(List("effekt"), s"Tuple${tps.size}"), tps)
872871

873872
/**
874873
* This is a compound production for
@@ -1100,78 +1099,109 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
11001099
case _ => fail(s"Expected identifier")
11011100
}
11021101

1103-
1104-
/**
1105-
* Types
1106-
*/
1107-
1108-
def valueType(): ValueType = valueType2(true)
1109-
1110-
/**
1111-
* Uses backtracking!
1102+
/*
1103+
* Type grammar by precedence:
1104+
*
1105+
* Type ::= Id ('[' Type ',' ... ']')? refType
1106+
*
1107+
* | '(' Type ',' ... ')' atomicType
1108+
* | '(' Type ')'
11121109
*
1113-
* This is not very efficient. To parse a value type, we first parse a block type,
1114-
* just to see that it either is no blocktype or it is not followed by an `at`
1115-
* and just "looked" like a block type...
1110+
* | Type '=>' Type ('/' SetType)? functionType
1111+
* | '(' Type ',' ... ')' ('{' Type '}')* '=>' Type ('/' SetType)?
11161112
*
1117-
* The parameter [[boxedAllowed]] controls whether on the right a dangling `at`
1118-
* can occur. This way we prevent parsing `() => S at {} at {}` and force users
1119-
* to manually parenthesize.
1113+
* | Type 'at' Id boxedType
1114+
* | Type 'at' '{' Id ',' ... '}'
1115+
* | Type ('/' SetType)?
1116+
*
1117+
* SetType ::= Type
1118+
* | '{' Type ',' ... '}'
11201119
*/
1121-
private def valueType2(boxedAllowed: Boolean): ValueType = nonterminal {
1122-
def boxedBlock = backtrack {
1123-
BoxedType(blockType2(false), `at` ~> captureSet())
1124-
}
1125-
if (boxedAllowed) { boxedBlock getOrElse atomicValueType() }
1126-
else atomicValueType()
1120+
def effects(): Effects =
1121+
nonterminal:
1122+
if peek(`{`) then Effects(many(refType, `{`, `,`, `}`))
1123+
else Effects(refType())
1124+
1125+
def maybeEffects(): Effects = {
1126+
nonterminal:
1127+
when(`/`) {
1128+
effects()
1129+
} {
1130+
Effects.Pure
1131+
}
11271132
}
11281133

1129-
def atomicValueType(): ValueType =
1134+
def refType(): TypeRef =
1135+
nonterminal:
1136+
TypeRef(idRef(), maybeTypeArgs())
1137+
1138+
// Parse atomic types: Tuples, parenthesized types, type references (highest precedence)
1139+
private def atomicType(): Type =
11301140
nonterminal:
11311141
peek.kind match {
1132-
case `(` => some(valueType, `(`, `,`, `)`) match {
1133-
case tpe :: Nil => tpe
1134-
case tpes => TupleTypeTree(tpes)
1135-
}
1136-
case _ => ValueTypeRef(idRef(), maybeTypeArgs())
1142+
case `(` =>
1143+
some(boxedType, `(`, `,`, `)`) match {
1144+
case tpe :: Nil => tpe
1145+
case tpes => TypeTuple(tpes)
1146+
}
1147+
case _ => refType()
1148+
}
1149+
1150+
// Parse function types (middle precedence)
1151+
private def functionType(): Type = {
1152+
// Complex function type: [T]*(Int, String)*{Exc} => Int / {Effect}
1153+
def functionTypeComplex = backtrack {
1154+
maybeTypeParams() ~ maybeValueTypes() ~ (maybeBlockTypeParams() <~ `=>`) ~ atomicType() ~ maybeEffects() match {
1155+
case tparams ~ vparams ~ bparams ~ t ~ effs => FunctionType(tparams, vparams, bparams, t, effs)
11371156
}
1157+
}
11381158

1159+
// Simple function type: Int => Int
1160+
def functionTypeSimple = backtrack {
1161+
refType() <~ `=>`
1162+
} map { tpe =>
1163+
FunctionType(Nil, List(tpe), Nil, atomicType(), maybeEffects())
1164+
}
11391165

1140-
/**
1141-
* Uses backtracking!
1142-
*
1143-
* TODO improve errors
1144-
* i.e. fail("Expected either a function type (e.g., (A) => B / {E} or => B) or an interface type (e.g., State[T]).")
1145-
*/
1146-
def blockType(): BlockType = blockType2(true)
1147-
private def blockType2(boxedAllowed: Boolean): BlockType =
1166+
// Try to parse each function type variant, fall back to basic type if none match
11481167
nonterminal:
1168+
functionTypeSimple orElse functionTypeComplex getOrElse atomicType()
1169+
}
11491170

1150-
def simpleFunType = backtrack {
1151-
ValueTypeRef(idRef(), maybeTypeArgs()) <~ `=>`
1152-
} map { tpe =>
1153-
FunctionType(Nil, List(tpe), Nil, valueType2(boxedAllowed), maybeEffects())
1171+
// Parse boxed types and effectfuls (lowest precedence)
1172+
// "Top-level" parser for a generic type.
1173+
private def boxedType(): Type = {
1174+
nonterminal:
1175+
// Parse the function type first
1176+
val tpe = functionType()
1177+
1178+
// TODO: these should probably be in a loop to parse as many `at`s and `\`s as possible?
1179+
val boxed = when(`at`) {
1180+
BoxedType(tpe, captureSet())
1181+
} {
1182+
tpe
11541183
}
11551184

1156-
def funType = backtrack {
1157-
maybeTypeParams() ~ maybeValueTypes() ~ (maybeBlockTypeParams() <~ `=>`) ~ valueType2(boxedAllowed) ~ maybeEffects() match {
1158-
case tparams ~ vparams ~ bparams ~ t ~ effs => FunctionType(tparams, vparams, bparams, t, effs)
1159-
}
1185+
if (peek(`/`)) {
1186+
Effectful(boxed, maybeEffects())
1187+
} else {
1188+
boxed
11601189
}
1161-
def parenthesized = backtrack { parens { blockType() } }
1190+
}
11621191

1163-
def interface() =
1164-
val res = interfaceType()
1165-
if peek(`/`) then
1166-
fail("Effects not allowed here. Maybe you mean to use a function type `() => T / E`?")
1167-
else res
1192+
// NOTE: ValueType, BlockType are just aliases for Type.
1193+
def blockType(): BlockType = boxedType()
1194+
def valueType(): ValueType = boxedType()
11681195

1169-
simpleFunType orElse funType orElse parenthesized getOrElse interface()
1196+
// Completely specialized for TypeRef: we only parse `refType` here, we don't go through the whole hierarchy.
1197+
// This results in slightly worse errors, but massively simplifies the design.
1198+
inline def blockTypeRef(): TypeRef = refType()
11701199

1171-
def interfaceType(): BlockTypeRef =
1172-
nonterminal:
1173-
BlockTypeRef(idRef(), maybeTypeArgs()): BlockTypeRef
1174-
// TODO error "Expected an interface type"
1200+
// Somewhat specialized: we parse a normal type, if it's not a ${tpe} / ${effs},
1201+
// then pretend the effect set is empty. This seems to work out fine :)
1202+
def effectful(): Effectful = boxedType() match
1203+
case eff: Effectful => eff
1204+
case tpe => Effectful(tpe, Effects.Pure)
11751205

11761206
def maybeTypeParams(): List[Id] =
11771207
nonterminal:
@@ -1181,15 +1211,15 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
11811211
nonterminal:
11821212
some(idDef, `[`, `,`, `]`)
11831213

1184-
def maybeBlockTypeParams(): List[(Option[IdDef], BlockType)] =
1214+
def maybeBlockTypeParams(): List[(Option[IdDef], Type)] =
11851215
nonterminal:
11861216
if peek(`{`) then blockTypeParams() else Nil
11871217

1188-
def blockTypeParams(): List[(Option[IdDef], BlockType)] =
1218+
def blockTypeParams(): List[(Option[IdDef], Type)] =
11891219
nonterminal:
11901220
someWhile(blockTypeParam(), `{`)
11911221

1192-
def blockTypeParam(): (Option[IdDef], BlockType) =
1222+
def blockTypeParam(): (Option[IdDef], Type) =
11931223
nonterminal:
11941224
braces { (backtrack { idDef() <~ `:` }, blockType()) }
11951225

@@ -1259,34 +1289,18 @@ class RecursiveDescent(positions: Positions, tokens: Seq[Token], source: Source)
12591289
nonterminal:
12601290
BlockParam(idDef(), when(`:`)(Some(blockType()))(None))
12611291

1262-
1263-
def maybeValueTypes(): List[ValueType] =
1292+
def maybeValueTypes(): List[Type] =
12641293
nonterminal:
12651294
if peek(`(`) then valueTypes() else Nil
12661295

1267-
def valueTypes(): List[ValueType] =
1296+
def valueTypes(): List[Type] =
12681297
nonterminal:
12691298
many(valueType, `(`, `,`, `)`)
12701299

12711300
def captureSet(): CaptureSet =
12721301
nonterminal:
12731302
CaptureSet(many(idRef, `{`, `,` , `}`))
12741303

1275-
def effectful(): Effectful =
1276-
nonterminal:
1277-
Effectful(valueType(), maybeEffects())
1278-
1279-
def maybeEffects(): Effects =
1280-
nonterminal:
1281-
when(`/`) { effects() } { Effects.Pure }
1282-
1283-
// TODO error "Expected an effect set"
1284-
def effects(): Effects =
1285-
nonterminal:
1286-
if peek(`{`) then Effects(many(interfaceType, `{`, `,`, `}`))
1287-
else Effects(interfaceType())
1288-
1289-
12901304
// Generic utility functions
12911305
// -------------------------
12921306
// ... for writing parsers.

effekt/shared/src/main/scala/effekt/Typer.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package typer
66
*/
77
import effekt.context.{Annotation, Annotations, Context, ContextOps}
88
import effekt.context.assertions.*
9-
import effekt.source.{AnyPattern, Def, Effectful, IgnorePattern, MatchGuard, MatchPattern, ModuleDecl, OpClause, Stmt, TagPattern, Term, Tree, resolve, symbol}
9+
import effekt.source.{AnyPattern, Def, Effectful, IgnorePattern, MatchGuard, MatchPattern, ModuleDecl, OpClause, Stmt, TagPattern, Term, Tree, resolve, resolveBlockRef, resolveValueType, resolveBlockType, symbol}
1010
import effekt.source.Term.BlockLiteral
1111
import effekt.symbols.*
1212
import effekt.symbols.builtins.*
@@ -150,7 +150,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
150150

151151
case c @ source.Do(effect, op, targs, vargs, bargs) =>
152152
// (1) first check the call
153-
val Result(tpe, effs) = checkOverloadedFunctionCall(c, op, targs map { _.resolve }, vargs, bargs, expected)
153+
val Result(tpe, effs) = checkOverloadedFunctionCall(c, op, targs map { _.resolveValueType }, vargs, bargs, expected)
154154
// (2) now we need to find a capability as the receiver of this effect operation
155155
// (2a) compute substitution for inferred type arguments
156156
val typeArgs = Context.annotatedTypeArgs(c)
@@ -168,21 +168,21 @@ object Typer extends Phase[NameResolved, Typechecked] {
168168
Result(tpe, effs ++ ConcreteEffects(List(effect)))
169169

170170
case c @ source.Call(t: source.IdTarget, targs, vargs, bargs) =>
171-
checkOverloadedFunctionCall(c, t.id, targs map { _.resolve }, vargs, bargs, expected)
171+
checkOverloadedFunctionCall(c, t.id, targs map { _.resolveValueType }, vargs, bargs, expected)
172172

173173
case c @ source.Call(source.ExprTarget(e), targs, vargs, bargs) =>
174174
val Result(tpe, funEffs) = checkExprAsBlock(e, None) match {
175175
case Result(b: FunctionType, capt) => Result(b, capt)
176176
case _ => Context.abort("Cannot infer function type for callee.")
177177
}
178178

179-
val Result(t, eff) = checkCallTo(c, "function", tpe, targs map { _.resolve }, vargs, bargs, expected)
179+
val Result(t, eff) = checkCallTo(c, "function", tpe, targs map { _.resolveValueType }, vargs, bargs, expected)
180180
Result(t, eff ++ funEffs)
181181

182182
// precondition: PreTyper translates all uniform-function calls to `Call`.
183183
// so the method calls here are actually methods calls on blocks as receivers.
184184
case c @ source.MethodCall(receiver, id, targs, vargs, bargs) =>
185-
checkOverloadedMethodCall(c, receiver, id, targs map { _.resolve }, vargs, bargs, expected)
185+
checkOverloadedMethodCall(c, receiver, id, targs map { _.resolveValueType }, vargs, bargs, expected)
186186

187187
case tree @ source.Region(name, body) =>
188188
val reg = tree.symbol
@@ -199,7 +199,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
199199

200200
// (1) extract all handled effects and capabilities
201201
val providedCapabilities: List[symbols.BlockParam] = handlers map Context.withFocus { h =>
202-
val effect: InterfaceType = h.effect.resolve
202+
val effect: InterfaceType = h.effect.resolveBlockRef
203203
val capability = h.capability.map { _.symbol }.getOrElse { Context.freshCapabilityFor(effect) }
204204
val tpe = capability.tpe.getOrElse { INTERNAL_ERROR("Block type annotation required") }
205205
Context.bind(capability, tpe, CaptureSet(capability.capture))
@@ -355,7 +355,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
355355
var handlerEffects: ConcreteEffects = Pure
356356

357357
// Extract interface and type arguments from annotated effect
358-
val tpe @ InterfaceType(constructor, targs) = sig.resolve
358+
val tpe @ InterfaceType(constructor, targs) = sig.resolveBlockRef
359359
val interface = constructor.asInterface // can only implement concrete interfaces
360360

361361
// (3) check all operations are covered

0 commit comments

Comments
 (0)