Skip to content

Commit 540e7af

Browse files
b-studiosdvdvgt
andauthored
Implement namespaced constructors and operations (#1092)
Constructors and operations are now defined in a namespace, named after the type. For instance: ``` type TrafficLight { Red(); Green(); Yellow() } ``` defines the constructor `TrafficLight::Red`, but also imports it as `Red`. The same holds for operations, which can now (in principle) be disambiguated like `cap.Nondet::flip()`. As a drive-by, I simplify the way operations are looked up and drop the unused effect on `Do`, which was in preparation for `do Nondet.flip()`, which now is `do Nondet::flip()`. --------- Co-authored-by: dvdvgt <[email protected]>
1 parent 4a21a53 commit 540e7af

File tree

15 files changed

+93
-51
lines changed

15 files changed

+93
-51
lines changed

effekt/jvm/src/test/scala/effekt/LSPTests.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,10 +1957,11 @@ class LSPTests extends FunSuite {
19571957

19581958
val outerBindings = hole.scope.outer.get.bindings
19591959

1960-
assertEquals(outerBindings.length, 3)
1961-
assertEquals(outerBindings(0).name, "e1")
1962-
assertEquals(outerBindings(1).name, "foo")
1963-
assertEquals(outerBindings(2).name, "e2")
1960+
assertEquals(outerBindings.length, 4)
1961+
assertEquals(outerBindings(0).name, "e1") // the type `effect e1(): Int / {}`
1962+
assertEquals(outerBindings(1).name, "e1") // the operation term
1963+
assertEquals(outerBindings(2).name, "foo")
1964+
assertEquals(outerBindings(3).name, "e2") // only the type since, but why?
19641965
}
19651966
}
19661967

@@ -2137,6 +2138,7 @@ class LSPTests extends FunSuite {
21372138
val hole = receivedHoles.head.holes.head
21382139
val outerScope = hole.scope.outer.get
21392140

2141+
assertEquals(outerScope.bindings.length, expectedBindings.length)
21402142
assertEquals(outerScope.bindings, expectedBindings)
21412143
}
21422144
}

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

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ object Namer extends Phase[Parsed, NameResolved] {
220220
ExternInterface(Context.nameFor(id), tps, decl)
221221
})
222222

223-
case d @ source.ExternDef(id, tparams, vparams, bparams, captures, ret, bodies, doc, span) => {
223+
case d @ source.ExternDef(id, tparams, vparams, bparams, captures, ret, bodies, doc, span) =>
224224
val name = Context.nameFor(id)
225225
val capt = resolve(captures)
226226
Context.define(id, Context scoped {
@@ -235,7 +235,6 @@ object Namer extends Phase[Parsed, NameResolved] {
235235

236236
ExternFunction(name, tps.unspan, vps.unspan, bps.unspan, tpe, eff, capt, bodies, d)
237237
})
238-
}
239238

240239
case d @ source.ExternResource(id, tpe, doc, span) =>
241240
val name = Context.nameFor(id)
@@ -348,14 +347,14 @@ object Namer extends Phase[Parsed, NameResolved] {
348347
}
349348
}
350349

351-
case source.InterfaceDef(id, tparams, operations, doc, span) =>
350+
case source.InterfaceDef(interfaceId, tparams, operations, doc, span) =>
352351
// symbol has already been introduced by the previous traversal
353-
val interface = Context.symbolOf(id).asInterface
352+
val interface = Context.symbolOf(interfaceId).asInterface
354353
interface.operations = operations.map {
355354
case op @ source.Operation(id, tparams, vparams, bparams, ret, doc, span) => Context.at(op) {
356355
val name = Context.nameFor(id)
357356

358-
Context.scopedWithName(id.name) {
357+
val opSym = Context.scopedWithName(id.name) {
359358
// the parameters of the interface are in scope
360359
interface.tparams.foreach { p => Context.bind(p) }
361360

@@ -372,10 +371,16 @@ object Namer extends Phase[Parsed, NameResolved] {
372371
// 2) the annotated type parameters on the concrete operation
373372
val (result, effects) = resolve(ret)
374373

375-
val opSym = Operation(name, interface.tparams ++ tps.unspan, resVparams, resBparams, result, effects, interface, op)
374+
Operation(name, interface.tparams ++ tps.unspan, resVparams, resBparams, result, effects, interface, op)
375+
}
376+
377+
// define in namespace ...
378+
Context.namespace(interfaceId.name) {
376379
Context.define(id, opSym)
377-
opSym
378380
}
381+
// ... and bind outside
382+
Context.bind(opSym)
383+
opSym
379384
}
380385
}
381386

@@ -385,19 +390,26 @@ object Namer extends Phase[Parsed, NameResolved] {
385390
}
386391

387392
// The type itself has already been resolved, now resolve constructors
388-
case d @ source.DataDef(id, tparams, ctors, doc, span) =>
393+
case d @ source.DataDef(typeId, tparams, ctors, doc, span) =>
389394
val data = d.symbol
390-
data.constructors = ctors map {
395+
val constructors = ctors map {
391396
case c @ source.Constructor(id, tparams, ps, doc, span) =>
392397
val constructor = Context scoped {
393398
val name = Context.nameFor(id)
394399
val tps = tparams map resolve
395400
Constructor(name, data.tparams ++ tps.unspan, Nil, data, c)
396401
}
397-
Context.define(id, constructor)
402+
// DataType::Constructor()
403+
Context.namespace(typeId.name) {
404+
Context.define(id, constructor)
405+
}
398406
constructor.fields = resolveFields(ps.unspan, constructor, false)
399407
constructor
400408
}
409+
// export DataType::{Constructor1, ...}
410+
constructors.foreach { c => Context.bind(c) }
411+
412+
data.constructors = constructors
401413

402414
// The record has been resolved as part of the preresolution step
403415
case d @ source.RecordDef(id, tparams, fs, doc, span) =>
@@ -526,8 +538,8 @@ object Namer extends Phase[Parsed, NameResolved] {
526538
vargs foreach resolve
527539
bargs foreach resolve
528540

529-
case source.Do(effect, target, targs, vargs, bargs, _) =>
530-
Context.resolveEffectCall(effect map resolveBlockRef, target)
541+
case source.Do(target, targs, vargs, bargs, _) =>
542+
Context.resolveEffectCall(target)
531543
targs foreach resolveValueType
532544
vargs foreach resolve
533545
bargs foreach resolve
@@ -1136,15 +1148,9 @@ trait NamerOps extends ContextOps { Context: Context =>
11361148
/**
11371149
* Resolves a potentially overloaded call to an effect
11381150
*/
1139-
private[namer] def resolveEffectCall(eff: Option[InterfaceType], id: IdRef): Unit = at(id) {
1140-
1141-
val syms = eff match {
1142-
case Some(tpe) =>
1143-
val interface = tpe.typeConstructor.asInterface
1144-
val operations = interface.operations.filter { op => op.name.name == id.name }
1145-
if (operations.isEmpty) Nil else List(operations.toSet)
1146-
case None => scope.lookupOperation(id.path, id.name)
1147-
}
1151+
private[namer] def resolveEffectCall(id: IdRef): Unit = at(id) {
1152+
1153+
val syms = scope.lookupOperation(id.path, id.name)
11481154

11491155
if (syms.isEmpty) {
11501156
abort(pretty"Cannot resolve effect operation ${id}")

effekt/shared/src/main/scala/effekt/Parser.scala

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ class Parser(tokens: Seq[Token], source: Source) {
300300
case Var(id, varSpan) =>
301301
val tgt = IdTarget(id)
302302
Return(Call(tgt, Nil, Nil, (BlockLiteral(Nil, vparams, bparams, body, body.span.synthesized)) :: Nil, varSpan), withSpan.synthesized)
303-
case Do(effect, id, targs, vargs, bargs, doSpan) =>
304-
Return(Do(effect, id, targs, vargs, bargs :+ BlockLiteral(Nil, vparams, bparams, body, body.span.synthesized), doSpan), withSpan.synthesized)
303+
case Do(id, targs, vargs, bargs, doSpan) =>
304+
Return(Do(id, targs, vargs, bargs :+ BlockLiteral(Nil, vparams, bparams, body, body.span.synthesized), doSpan), withSpan.synthesized)
305305
case term =>
306306
Return(Call(ExprTarget(term), Nil, Nil, (BlockLiteral(Nil, vparams, bparams, body, body.span.synthesized)) :: Nil, term.span.synthesized), withSpan.synthesized)
307307
}
@@ -830,7 +830,7 @@ class Parser(tokens: Seq[Token], source: Source) {
830830
def doExpr(): Term =
831831
nonterminal:
832832
(`do` ~> idRef()) ~ arguments() match {
833-
case id ~ (targs, vargs, bargs) => Do(None, id, targs, vargs, bargs, span())
833+
case id ~ (targs, vargs, bargs) => Do(id, targs, vargs, bargs, span())
834834
}
835835

836836
/*
@@ -1290,7 +1290,6 @@ class Parser(tokens: Seq[Token], source: Source) {
12901290
val target = id.getOrElse(IdRef(Nil, "s", id.span.synthesized))
12911291
val doLits = strs.map { s =>
12921292
Do(
1293-
None,
12941293
IdRef(Nil, "literal", s.span.synthesized),
12951294
Nil,
12961295
List(ValueArg.Unnamed(StringLit(s.unspan, s.span))),
@@ -1300,7 +1299,6 @@ class Parser(tokens: Seq[Token], source: Source) {
13001299
}
13011300
val doSplices = args.map { arg =>
13021301
Do(
1303-
None,
13041302
IdRef(Nil, "splice", arg.span.synthesized),
13051303
Nil,
13061304
List(ValueArg.Unnamed(arg.unspan)),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ object Typer extends Phase[NameResolved, Typechecked] {
187187
case c @ source.Select(receiver, field, _) =>
188188
checkOverloadedFunctionCall(c, field, Nil, List(source.ValueArg.Unnamed(receiver)), Nil, expected)
189189

190-
case c @ source.Do(effect, op, targs, vargs, bargs, _) =>
190+
case c @ source.Do(op, targs, vargs, bargs, _) =>
191191
// (1) first check the call
192192
val Result(tpe, effs) = checkOverloadedFunctionCall(c, op, targs map { _.resolveValueType }, vargs, bargs, expected)
193193
// (2) now we need to find a capability as the receiver of this effect operation

effekt/shared/src/main/scala/effekt/core/Transformer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] {
508508
case c @ source.Call(s: source.ExprTarget, targs, vargs, bargs, _) =>
509509
Context.panic("Should not happen. Unbox should have been inferred.")
510510

511-
case source.Do(effect, id, targs, vargs, bargs, _) =>
511+
case source.Do(id, targs, vargs, bargs, _) =>
512512
Context.panic("Should have been translated away (to explicit selection `@CAP.op()`) by capability passing.")
513513
}
514514

effekt/shared/src/main/scala/effekt/source/AnnotateCaptures.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ object AnnotateCaptures extends Phase[Typechecked, Typechecked], Query[Unit, Cap
5151
}
5252
query(term) ++ capt
5353

54-
case t @ source.Do(effect, op, targs, vargs, bargs, _) =>
54+
case t @ source.Do(op, targs, vargs, bargs, _) =>
5555
val cap = Context.annotation(Annotations.CapabilityReceiver, t)
5656
combineAll(vargs.map(query)) ++ combineAll(bargs.map(query)) ++ CaptureSet(cap.capture)
5757

effekt/shared/src/main/scala/effekt/source/ExplicitCapabilities.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object ExplicitCapabilities extends Phase[Typechecked, Typechecked], Rewrite {
4242
override def expr(using Context) = {
4343

4444
// an effect call -- translate to method call on the inferred capability
45-
case c @ Do(effect, id, targs, vargs, bargs, span) =>
45+
case c @ Do(id, targs, vargs, bargs, span) =>
4646
val transformedValueArgs = vargs.map(rewrite)
4747
val transformedBlockArgs = bargs.map(rewrite)
4848

@@ -130,7 +130,7 @@ object ExplicitCapabilities extends Phase[Typechecked, Typechecked], Rewrite {
130130
source.BlockLiteral(tps, vps, bps ++ capParams, rewrite(body), span)
131131
}
132132

133-
override def rewrite(body: ExternBody)(using context.Context): ExternBody =
133+
override def rewrite(body: ExternBody)(using context.Context): ExternBody =
134134
body match {
135135
case b @ source.ExternBody.StringExternBody(ff, body, span) =>
136136
val rewrittenTemplate =
@@ -139,7 +139,7 @@ object ExplicitCapabilities extends Phase[Typechecked, Typechecked], Rewrite {
139139
)
140140
b.copy(template = rewrittenTemplate)
141141
case b @ source.ExternBody.EffektExternBody(ff, body, span) =>
142-
val rewrittenBody = rewrite(body)
142+
val rewrittenBody = rewrite(body)
143143
b.copy(body = rewrittenBody)
144144
case u: source.ExternBody.Unsupported => u
145145
}

effekt/shared/src/main/scala/effekt/source/Tree.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,8 @@ enum Term extends Tree {
524524

525525
/**
526526
* A call to an effect operation, i.e., `do raise()`.
527-
*
528-
* The [[effect]] is the optionally annotated effect type (not possible in source ATM). In the future, this could
529-
* look like `do Exc.raise()`, or `do[Exc] raise()`, or do[Exc].raise(), or simply Exc.raise() where Exc is a type.
530527
*/
531-
case Do(effect: Option[TypeRef], id: IdRef, targs: List[ValueType], vargs: List[ValueArg], bargs: List[Term], span: Span) extends Term, Reference
528+
case Do(id: IdRef, targs: List[ValueType], vargs: List[ValueArg], bargs: List[Term], span: Span) extends Term, Reference
532529

533530
/**
534531
* A call to either an expression, i.e., `(box { () => ... })()`; or a named function, i.e., `foo()`

effekt/shared/src/main/scala/effekt/symbols/Scope.scala

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ case class Bindings(
5050

5151
def getNamespace(name: String): Option[Bindings] =
5252
namespaces.get(name)
53-
54-
def operations: Map[String, Set[Operation]] =
55-
types.values.toSet.flatMap {
56-
case BlockTypeConstructor.Interface(_, _, operations, _) => operations.toSet
57-
case _ => Set.empty
58-
}.groupMap(_.name.name)(op => op)
5953
}
6054

6155
object Bindings {
@@ -228,12 +222,13 @@ object scopes {
228222

229223
def lookupOverloadedMethod(id: IdRef, filter: TermSymbol => Boolean)(using ErrorReporter): List[Set[Operation]] =
230224
all(id.path, scope) { namespace =>
231-
namespace.operations.getOrElse(id.name, Set.empty).filter(filter)
225+
namespace.terms.getOrElse(id.name, Set.empty).collect { case op: Operation if filter(op) => op }
232226
}
233227

228+
// the last element in the path can also be the type of the name.
234229
def lookupOperation(path: List[String], name: String)(using ErrorReporter): List[Set[Operation]] =
235230
all(path, scope) { namespace =>
236-
namespace.operations.getOrElse(name, Set.empty)
231+
namespace.terms.getOrElse(name, Set.empty).collect { case op: Operation => op }
237232
}.filter { namespace => namespace.nonEmpty }
238233

239234
def lookupFunction(path: List[String], name: String)(using ErrorReporter): List[Set[Callable]] =

effekt/shared/src/main/scala/effekt/typer/UnboxInference.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ object UnboxInference extends Phase[NameResolved, NameResolved] {
8585
case s @ Select(recv, name, span) =>
8686
C.abort("selection on blocks not supported yet.")
8787

88-
case Do(effect, id, targs, vargs, bargs, span) =>
89-
Do(effect, id, targs, vargs.map(rewriteAsExpr), bargs.map(rewriteAsBlock), span)
88+
case Do(id, targs, vargs, bargs, span) =>
89+
Do(id, targs, vargs.map(rewriteAsExpr), bargs.map(rewriteAsBlock), span)
9090

9191
case Call(fun, targs, vargs, bargs, span) =>
9292
Call(rewrite(fun), targs, vargs.map(rewriteAsExpr), bargs.map(rewriteAsBlock), span)

0 commit comments

Comments
 (0)