Skip to content

Commit 5eb9423

Browse files
Name-based implicits in Effekt (#1380)
This implements implicits similar to > Daan Leijen, Tim Whiting: Syntactic Implicit Parameters with Static Overloading It also implements their use to get source positions in the code. ### Syntax and Intended Behaviour Putting a `?` in front of a value/block parameter makes it potentially implicit. If *trailing* implicit parameters are missing, an argument is generated as follows (e.g.): - value argument called `$x`: `$x` - value argument called `sourcePosition`: `SourcePosition("somefilename.effekt",10,3,10,6)` (corresponding to the source position of the called function, ~~without~~ with argument list) - value argument called `callId`: A statically unique integer for *this* call (unique within an Effekt compiler run). - value argument of type `(...) => ... at {...}`: instantiated to `box {...}` with whatever the block argument would be instantiated to. - block argument called `$foo` of type `(A){B=>C} => D` : `{ (a: A){b: B => C} => $foo(a){b} }` (eta-expanded to allow for this call to also receive implicit arguments) - block argument with an interface type, called `$x`: `$x` ### Implementation strategy - In `Namer`, - find the set of all implicit parameters that any of the overloads could require (i.e. for all functions of that name, all implicit parameters) and generate the source for them already. - then name-resolve them, but collect errors and just store them (for now) - in `Typer`, - instantiate the arguments (i.e., currently, hardcoded copy of the block literal and corresponding annotations, or just copying them), refreshing them and typechecking them against their respective parameters - only happens if at this point, no errors were reported in this case (otherwise, aborts) - annotate the instantiated arguments - in `ExplicitCapabilities`, actually change the source to pass the annotated arguments explicitly. ### TODO and limitations - [x] ~~Check if this works for more than the existing examples (find at least the worst bugs)~~ The tests should be testing a good part of possible uses now. - [x] This *will* cause *Typer* to run indefinitely or stack-overflow if the search is not terminated by a type/name error. We could try to catch those cases somehow, but this is nontrivial (and will reject valid programs). - Opinions? - The recursion is ~~quite indirect (and not actual recursion) in typer, and~~ (we can check for recursive uses when checking implicit arguments after instantiating them - this is separate now anyway) at a point where we don't necessarily have the concrete types (due to unification), so checking for "decreasing type size" or similar is potentially hard. - Now aborts if, at the same name, we have 10 levels where the expected type does not get smaller. - [x] Syntax bikeshedding - [ ] If merging this, we might want to change #1381 / #1123 s.t. they provide only the definitions for base and recursive cases (not for all types used etc). --------- Co-authored-by: Jonathan Immanuel Brachthäuser <jonathan.brachthaeuser@uni-tuebingen.de>
1 parent 7e5beee commit 5eb9423

60 files changed

Lines changed: 1167 additions & 93 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

effekt/shared/src/main/scala/effekt/Lexer.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ enum TokenKind {
130130
case `...`
131131
case `^^`
132132
case `^`
133+
case `?`
133134

134135
// keywords
135136
case `let`
@@ -472,6 +473,7 @@ class Lexer(source: Source) extends Iterator[Token] {
472473

473474
case ('*', '=') => advanceWith(TokenKind.`*=`)
474475
case ('*', _) => advanceWith(TokenKind.`*`)
476+
case ('?', _) => advanceWith(TokenKind.`?`)
475477

476478
case ('$', '{') =>
477479
interpolationDepths.push(depthTracker.braces + 1)

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

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ package namer
77
import effekt.context.{Annotations, Context, ContextOps}
88
import effekt.context.assertions.*
99
import effekt.typer.Substitutions
10-
import effekt.source.{Def, Id, IdDef, IdRef, Many, MatchGuard, ModuleDecl, Term, Tree, sourceOf}
10+
import effekt.source.{Def, Id, IdDef, IdRef, Many, MatchGuard, ModuleDecl, Term, Tree, sourceOf, GenerateImplicitArgs}
1111
import effekt.symbols.*
1212
import effekt.util.messages.ErrorMessageReifier
1313
import effekt.symbols.scopes.*
14+
import effekt.context.Try
1415

1516
import scala.annotation.tailrec
1617
import scala.collection.mutable
1718
import scala.util.DynamicVariable
19+
import effekt.util.RequirementLevel
1820

1921
/**
2022
* The output of this phase: a mapping from source identifier to symbol
@@ -337,6 +339,7 @@ object Namer extends Phase[Parsed, NameResolved] {
337339

338340
// FunDef and InterfaceDef have already been resolved as part of the module declaration
339341
case f @ source.FunDef(id, tparams, vparams, bparams, captures, ret, body, doc, span) =>
342+
checkImplicitParams(vparams.unspan); checkImplicitParams(bparams.unspan)
340343
val sym = f.symbol
341344
Context.scopedWithName(id.name) {
342345
sym.tparams.foreach { p => Context.bind(p) }
@@ -347,6 +350,7 @@ object Namer extends Phase[Parsed, NameResolved] {
347350
}
348351

349352
case f @ source.ExternDef(id, tparams, vparams, bparams, captures, ret, bodies, doc, span) =>
353+
checkImplicitParams(vparams.unspan); checkImplicitParams(bparams.unspan)
350354
val sym = f.symbol
351355
Context.scopedWithName(id.name) {
352356
sym.tparams.foreach { p => Context.bind(p) }
@@ -364,6 +368,7 @@ object Namer extends Phase[Parsed, NameResolved] {
364368
val interface = Context.symbolOf(interfaceId).asInterface
365369
interface.operations = operations.map {
366370
case op @ source.Operation(id, tparams, vparams, bparams, ret, doc, span) => Context.at(op) {
371+
checkImplicitParams(vparams); checkImplicitParams(bparams)
367372
val name = Context.nameFor(id)
368373

369374
val opSym = Context.scopedWithName(id.name) {
@@ -445,6 +450,47 @@ object Namer extends Phase[Parsed, NameResolved] {
445450
resolve(a.value)
446451
}
447452

453+
/**
454+
* Checks that there are no implicit parameters if there shouldn't be (or there are if there must be).
455+
* Also checks that there are no non-implicit parameters after implicit ones.
456+
*/
457+
@tailrec
458+
def checkImplicitParams(l: List[source.Param], implicitsAllowed: RequirementLevel = RequirementLevel.Optional)(using Context): Unit = (l, implicitsAllowed) match {
459+
case ((p@(source.ValueParam(_, _, true, _) | source.BlockParam(_, _, true, _))) :: tl, RequirementLevel.Forbidden) =>
460+
Context.at(p) {
461+
Context.error(pretty"Implicit parameter ${l.head.span.text.getOrElse(l.head.id.name)} can never be passed implicitly to here.")
462+
}
463+
case ((p@(source.ValueParam(_, _, false, _) | source.BlockParam(_, _, false, _))) :: tl, RequirementLevel.Required) =>
464+
Context.at(p) {
465+
Context.error(pretty"Parameter ${l.head.span.text.getOrElse(l.head.id.name)} needs to be implicit so earlier implicit parameters can be passed implicitly.")
466+
}
467+
case ((source.ValueParam(_, _, true, _) | source.BlockParam(_, _, true, _)) :: tl, RequirementLevel.Optional) =>
468+
checkImplicitParams(tl, RequirementLevel.Required) // require all arguments after an implicit one to be implicit
469+
case (_ :: tl, _) =>
470+
checkImplicitParams(tl, implicitsAllowed)
471+
case (Nil, _) => ()
472+
}
473+
474+
/**
475+
* Name-resolves all implicit arguments that could be interesting for calling the given function symbol.
476+
*
477+
* Errors while doing so are residualized into the respective [[ImplicitContext]] and only reported
478+
* during overload resolution later.
479+
*/
480+
def resolveImplicits(id: source.Id)(using Context): Unit = {
481+
Context.symbolOf(id) match {
482+
case symbols.CallTarget(syms, cs) =>
483+
cs.foreach {
484+
case (b, c@ImplicitContext(vimpls, bimpls)) if !c.resolved =>
485+
c.resolved = true // set as resolved first, so recursive calls do not continue doing so.
486+
c.values = vimpls.map { case k -> mv => k -> GenerateImplicitArgs.runPhaseOn(k, mv){ v => Try { resolve(v) } } }
487+
c.blocks = bimpls.map { case k -> mb => k -> GenerateImplicitArgs.runPhaseOn(k, mb){ b => Try { resolve(b) } } }
488+
case _ => ()
489+
}
490+
case _ => ()
491+
}
492+
}
493+
448494
def resolve(t: source.Term)(using Context): Unit = Context.focusing(t) {
449495

450496
case source.Literal(value, tpe, _) => ()
@@ -491,14 +537,16 @@ object Namer extends Phase[Parsed, NameResolved] {
491537

492538
case tree @ source.Region(name, body, _) =>
493539
val regionName = Name.local(name.name)
494-
val reg = BlockParam(regionName, Some(builtins.TRegion), CaptureParam(regionName), tree)
540+
val reg = BlockParam(regionName, Some(builtins.TRegion), CaptureParam(regionName), isImplicit = false, tree)
495541
Context.define(name, reg)
496542
Context scoped {
497543
Context.bindBlock(reg)
498544
resolve(body)
499545
}
500546

501547
case f @ source.BlockLiteral(tparams, vparams, bparams, stmt, _) =>
548+
checkImplicitParams(vparams, RequirementLevel.Forbidden)
549+
checkImplicitParams(bparams, RequirementLevel.Forbidden)
502550
Context scoped {
503551
val tps = tparams map resolve
504552
val vps = vparams map resolve
@@ -551,19 +599,21 @@ object Namer extends Phase[Parsed, NameResolved] {
551599
then Context.abort(pp"Cannot resolve function ${target}, called on an expression.")
552600
}
553601
}
602+
resolveImplicits(target)
554603
targs foreach resolveValueType
555604
vargs foreach resolve
556605
bargs foreach resolve
557606

558607
case source.Do(target, targs, vargs, bargs, _) =>
559608
Context.resolveEffectCall(target)
609+
resolveImplicits(target)
560610
targs foreach resolveValueType
561611
vargs foreach resolve
562612
bargs foreach resolve
563613

564614
case source.Call(target, targs, vargs, bargs, _) =>
565615
Context.focusing(target) {
566-
case source.IdTarget(id) => Context.resolveFunctionCalltarget(id)
616+
case source.IdTarget(id) => Context.resolveFunctionCalltarget(id); resolveImplicits(id)
567617
case source.ExprTarget(expr) => resolve(expr)
568618
}
569619
targs foreach resolveValueType
@@ -666,19 +716,19 @@ object Namer extends Phase[Parsed, NameResolved] {
666716
* Used for fields where "please wrap this in braces" is not good advice to be told by [[resolveValueType]].
667717
*/
668718
def resolveNonfunctionValueParam(p: source.ValueParam)(using Context): ValueParam = {
669-
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = false)), decl = p)
719+
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = false)), p.isImplicit, decl = p)
670720
Context.assignSymbol(p.id, sym)
671721
sym
672722
}
673723

674724
def resolve(p: source.ValueParam)(using Context): ValueParam = {
675-
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = true)), decl = p)
725+
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = true)), p.isImplicit, decl = p)
676726
Context.assignSymbol(p.id, sym)
677727
sym
678728
}
679729
def resolve(p: source.BlockParam)(using Context): BlockParam = {
680730
val name = Name.local(p.id)
681-
val sym: BlockParam = BlockParam(name, p.tpe.map { tpe => resolveBlockType(tpe, isParam = true) }, CaptureParam(name), p)
731+
val sym: BlockParam = BlockParam(name, p.tpe.map { tpe => resolveBlockType(tpe, isParam = true) }, CaptureParam(name), p.isImplicit, p)
682732
Context.assignSymbol(p.id, sym)
683733
sym
684734
}
@@ -719,7 +769,7 @@ object Namer extends Phase[Parsed, NameResolved] {
719769
case source.IgnorePattern(_) => Nil
720770
case source.LiteralPattern(lit, _) => Nil
721771
case source.AnyPattern(id, _) =>
722-
val p = ValueParam(Name.local(id), None, decl = id)
772+
val p = ValueParam(Name.local(id), None, isImplicit = false, decl = id)
723773
Context.assignSymbol(id, p)
724774
List(p)
725775
case source.TagPattern(id, patterns, _) =>
@@ -1081,7 +1131,11 @@ trait NamerOps extends ContextOps { Context: Context =>
10811131

10821132
val syms2 = if (syms.isEmpty) scope.lookupFunction(id.path, id.name) else syms
10831133

1084-
if (syms2.nonEmpty) { assignSymbol(id, CallTarget(syms2.asInstanceOf)); true } else { false }
1134+
if (syms2.nonEmpty) {
1135+
val syms3 = syms2.asInstanceOf[List[Set[BlockSymbol]]]
1136+
assignSymbol(id, CallTarget(syms3, GenerateImplicitArgs.lookupPotentialImplicits(syms3, scope.scope)))
1137+
true
1138+
} else { false }
10851139
}
10861140

10871141
private[namer] def resolveOverloadedFunction(id: IdRef): Boolean = at(id) {
@@ -1092,7 +1146,11 @@ trait NamerOps extends ContextOps { Context: Context =>
10921146
// lookup first block param and do not collect multiple since we do not (yet?) permit overloading on block parameters
10931147
val syms3 = if (syms2.isEmpty) List(scope.lookupFirstBlockParam(id.path, id.name)) else syms2
10941148

1095-
if (syms3.nonEmpty) { assignSymbol(id, CallTarget(syms3.asInstanceOf)); true } else { false }
1149+
if (syms3.nonEmpty) {
1150+
val syms4 = syms3.asInstanceOf[List[Set[BlockSymbol]]]
1151+
assignSymbol(id, CallTarget(syms4, GenerateImplicitArgs.lookupPotentialImplicits(syms4, scope.scope)))
1152+
true
1153+
} else { false }
10961154
}
10971155

10981156
/**
@@ -1116,7 +1174,7 @@ trait NamerOps extends ContextOps { Context: Context =>
11161174
// Always abort with the generic message
11171175
abort(pretty"Cannot find a function named `${id}`.")
11181176
}
1119-
assignSymbol(id, CallTarget(blocks))
1177+
assignSymbol(id, CallTarget(blocks, GenerateImplicitArgs.lookupPotentialImplicits(blocks, scope.scope)))
11201178
}
11211179
}
11221180

@@ -1170,7 +1228,8 @@ trait NamerOps extends ContextOps { Context: Context =>
11701228
abort(pretty"Cannot resolve field access ${id}")
11711229
}
11721230

1173-
assignSymbol(id, CallTarget(syms.asInstanceOf))
1231+
val bsyms = syms.asInstanceOf[List[Set[BlockSymbol]]]
1232+
assignSymbol(id, CallTarget(bsyms, GenerateImplicitArgs.lookupPotentialImplicits(bsyms, scope.scope)))
11741233
}
11751234

11761235
/**
@@ -1184,7 +1243,8 @@ trait NamerOps extends ContextOps { Context: Context =>
11841243
abort(pretty"Cannot resolve effect operation ${id}")
11851244
}
11861245

1187-
assignSymbol(id, CallTarget(syms.asInstanceOf))
1246+
val bsyms = syms.asInstanceOf[List[Set[BlockSymbol]]]
1247+
assignSymbol(id, CallTarget(bsyms, GenerateImplicitArgs.lookupPotentialImplicits(bsyms, scope.scope)))
11881248
}
11891249

11901250
/**

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ class Parser(tokens: Seq[Token], source: Source) {
195195
def peek(offset: Int, kind: TokenKind): Boolean =
196196
peek(offset).kind == kind
197197

198+
def skipIf(kind: TokenKind): Boolean =
199+
if (peek.kind == kind) { skip(); true } else { false }
200+
198201
def hasNext(): Boolean = position < tokens.length
199202
def next(): Token =
200203
val t = tokens(position).failOnErrorToken(position)
@@ -331,8 +334,8 @@ class Parser(tokens: Seq[Token], source: Source) {
331334
// Simple case: all patterns are just variable names (or ignored), no guards, no fallback
332335
// Desugar to: call { (x, y, _, ...) => body }
333336
val vparams: List[ValueParam] = patterns.unspan.map {
334-
case AnyPattern(id, span) => ValueParam(id, None, span)
335-
case IgnorePattern(span) => ValueParam(IdDef(s"__ignored", span.synthesized), None, span)
337+
case AnyPattern(id, span) => ValueParam(id, None, isImplicit = false, span)
338+
case IgnorePattern(span) => ValueParam(IdDef(s"__ignored", span.synthesized), None, isImplicit = false, span)
336339
case _ => sys.error("impossible: checked above")
337340
}
338341
BlockLiteral(Nil, vparams, Nil, body, body.span.synthesized)
@@ -345,7 +348,7 @@ class Parser(tokens: Seq[Token], source: Source) {
345348
val argSpans = patternList.map(_.span)
346349

347350
val vparams: List[ValueParam] = names.zip(argSpans).map { (name, span) =>
348-
ValueParam(IdDef(name, span.synthesized), None, span.synthesized)
351+
ValueParam(IdDef(name, span.synthesized), None, isImplicit = false, span.synthesized)
349352
}
350353
val scrutinees = names.zip(argSpans).map { (name, span) =>
351354
Var(IdRef(Nil, name, span.synthesized), span.synthesized)
@@ -931,7 +934,7 @@ class Parser(tokens: Seq[Token], source: Source) {
931934
nonterminal:
932935
`with` ~> backtrack(idDef() <~ `:`) ~ implementation() match {
933936
case capabilityName ~ impl =>
934-
val capability = capabilityName map { name => BlockParam(name, Some(impl.interface), name.span.synthesized): BlockParam }
937+
val capability = capabilityName map { name => BlockParam(name, Some(impl.interface), isImplicit = false, name.span.synthesized): BlockParam }
935938
Handler(capability.unspan, impl, span())
936939
}
937940

@@ -1284,7 +1287,7 @@ class Parser(tokens: Seq[Token], source: Source) {
12841287
val names = List.tabulate(argSpans.length){ n => s"__arg${n}" }
12851288
BlockLiteral(
12861289
Nil,
1287-
names.zip(argSpans).map { (name, span) => ValueParam(IdDef(name, span.synthesized), None, span.synthesized) },
1290+
names.zip(argSpans).map { (name, span) => ValueParam(IdDef(name, span.synthesized), None, isImplicit = false, span.synthesized) },
12881291
Nil,
12891292
Return(
12901293
Match(
@@ -1654,7 +1657,7 @@ class Parser(tokens: Seq[Token], source: Source) {
16541657

16551658
def lambdaParams(): (List[Id], List[ValueParam], List[BlockParam]) =
16561659
nonterminal:
1657-
if isVariable then (Nil, List(ValueParam(idDef(), None, span())), Nil) else paramsOpt()
1660+
if isVariable then (Nil, List(ValueParam(idDef(), None, isImplicit=false, span())), Nil) else paramsOpt()
16581661

16591662
def params(): (Many[Id], Many[ValueParam], Many[BlockParam]) =
16601663
nonterminal:
@@ -1688,11 +1691,13 @@ class Parser(tokens: Seq[Token], source: Source) {
16881691

16891692
def valueParam(): ValueParam =
16901693
nonterminal:
1691-
ValueParam(idDef(), Some(valueTypeAnnotation()), span())
1694+
val isImplicit = skipIf(`?`)
1695+
ValueParam(idDef(), Some(valueTypeAnnotation()), isImplicit, span())
16921696

16931697
def valueParamOpt(): ValueParam =
16941698
nonterminal:
1695-
ValueParam(idDef(), maybeValueTypeAnnotation(), span())
1699+
val isImplicit = skipIf(`?`)
1700+
ValueParam(idDef(), maybeValueTypeAnnotation(), isImplicit, span())
16961701

16971702
def maybeBlockParams(): Many[BlockParam] =
16981703
nonterminal:
@@ -1712,11 +1717,13 @@ class Parser(tokens: Seq[Token], source: Source) {
17121717

17131718
def blockParam(): BlockParam =
17141719
nonterminal:
1715-
BlockParam(idDef(), Some(blockTypeAnnotation()), span())
1720+
val isImplicit = skipIf(`?`)
1721+
BlockParam(idDef(), Some(blockTypeAnnotation()), isImplicit, span())
17161722

17171723
def blockParamOpt(): BlockParam =
17181724
nonterminal:
1719-
BlockParam(idDef(), when(`:`)(Some(blockType()))(None), span())
1725+
val isImplicit = skipIf(`?`)
1726+
BlockParam(idDef(), when(`:`)(Some(blockType()))(None), isImplicit, span())
17201727

17211728
def maybeValueTypes(): Many[Type] =
17221729
nonterminal:

0 commit comments

Comments
 (0)