Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a0d927f
Initial draft
marzipankaiser Apr 15, 2026
3713952
Instantiate implicit parameters late
marzipankaiser Apr 15, 2026
6132a0f
Remove TODOs
marzipankaiser Apr 15, 2026
e184923
Fix recursive usage of implicits
marzipankaiser Apr 15, 2026
5b301e2
Fix case where there are implicit and explicit arguments
marzipankaiser Apr 15, 2026
6dcaf30
Add missing annotation for block argument
marzipankaiser Apr 15, 2026
01ac986
Split out generation of implicits into separate methods
marzipankaiser Apr 15, 2026
99e07aa
Add sourcePosition implicit arguments
marzipankaiser Apr 15, 2026
d187654
Add basic check for order between implicits and non-implicits
marzipankaiser Apr 15, 2026
1c82dd6
Add some tests for name-based implicits
marzipankaiser Apr 15, 2026
a7d3676
Fix name-based implicits on operations (on objects/effects)
marzipankaiser Apr 15, 2026
da1119a
Fix generated names, Report errors during namechecking of impicit arg…
marzipankaiser Apr 15, 2026
111b691
Add another example (implementing type-level plus and toInt)
marzipankaiser Apr 15, 2026
5695bfc
Make ints long to make the llvm backend happy
marzipankaiser Apr 15, 2026
8725374
Bind intermediate value in example
marzipankaiser Apr 15, 2026
f8930cd
Fix typelevel example for other inputs
marzipankaiser Apr 15, 2026
87b14ac
Print implicit arguments correctly in DeclPrinter
marzipankaiser Apr 15, 2026
bd7bacc
Move initial generation of implicit arguments to separate object
marzipankaiser Apr 15, 2026
8c944c1
Move implicit-related helpers from typer too the object, too
marzipankaiser Apr 15, 2026
b4dcaf3
Add some documentation
marzipankaiser Apr 15, 2026
da15c20
Add example with return-type overloading
marzipankaiser Apr 16, 2026
83ab24a
Add type-tag example variant with return type overloading
marzipankaiser Apr 16, 2026
8b85f18
Add example using implicit interface arguments
marzipankaiser Apr 16, 2026
2cf8a16
Slightly improve error messages for implicits
marzipankaiser Apr 16, 2026
0f9a957
Better error message location for implicits in block literals (disall…
marzipankaiser Apr 16, 2026
1e59302
Stop recursion if the types aren't getting smaller for multiple levels
marzipankaiser Apr 16, 2026
9e756b5
Add type annotation to make the example work
marzipankaiser Apr 16, 2026
8d968bb
Make it more obvious we are only interested in side effects and error…
marzipankaiser Apr 16, 2026
ba63f67
Can't use () so use _
marzipankaiser Apr 16, 2026
ba3ea3e
Refactor: Use type for incomplete stencils in ImplicitContext, simplify
marzipankaiser Apr 17, 2026
31e1ef5
Generate nicer (nested) errors for implicit instantiation
marzipankaiser Apr 17, 2026
db261c2
Further improve error message (include type)
marzipankaiser Apr 17, 2026
233aed4
Even better error messages, now with explanations for special cases
marzipankaiser Apr 17, 2026
7d78a4a
Always have a parameter index
marzipankaiser Apr 17, 2026
7ba6655
typo
marzipankaiser Apr 17, 2026
c30787f
Refactor: Remove now-obsolete type parameter
marzipankaiser Apr 17, 2026
55ab960
Refactor: Move stencils into GenerateImplicitArgs, add some structur
marzipankaiser Apr 17, 2026
b84341d
Expand boxes in implicit value arguments
marzipankaiser Apr 17, 2026
5b2b0d9
Fix error message for boxed types
marzipankaiser Apr 17, 2026
b26c6a8
Remove ReifiedType, there is *TypeTree already...
marzipankaiser Apr 17, 2026
e92b903
Simplify generation of refreshed block literal
marzipankaiser Apr 18, 2026
85be025
Simplify generation of value args
marzipankaiser Apr 18, 2026
9e3c2e1
Initial draft language tour and minor fix
marzipankaiser Apr 20, 2026
67de2c7
Update effekt/shared/src/main/scala/effekt/Namer.scala
marzipankaiser Apr 20, 2026
1aa3829
Minor improvements in the name-based implicits tour
marzipankaiser Apr 21, 2026
319e60d
Fix example by unboxing manually
marzipankaiser Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions effekt/shared/src/main/scala/effekt/Lexer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ enum TokenKind {
case `...`
case `^^`
case `^`
case `?`

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

case ('*', '=') => advanceWith(TokenKind.`*=`)
case ('*', _) => advanceWith(TokenKind.`*`)
case ('?', _) => advanceWith(TokenKind.`?`)

case ('$', '{') =>
interpolationDepths.push(depthTracker.braces + 1)
Expand Down
84 changes: 72 additions & 12 deletions effekt/shared/src/main/scala/effekt/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ package namer
import effekt.context.{Annotations, Context, ContextOps}
import effekt.context.assertions.*
import effekt.typer.Substitutions
import effekt.source.{Def, Id, IdDef, IdRef, Many, MatchGuard, ModuleDecl, Term, Tree, sourceOf}
import effekt.source.{Def, Id, IdDef, IdRef, Many, MatchGuard, ModuleDecl, Term, Tree, sourceOf, GenerateImplicitArgs}
import effekt.symbols.*
import effekt.util.messages.ErrorMessageReifier
import effekt.symbols.scopes.*
import effekt.context.Try

import scala.annotation.tailrec
import scala.collection.mutable
import scala.util.DynamicVariable
import effekt.util.RequirementLevel

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

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

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

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

/**
* Checks that there are no implicit parameters if there shouldn't be (or there are if there must be).
* Also checks that there are no non-implicit parameters after implicit ones.
*/
@tailrec
def checkImplicitParams(l: List[source.Param], implicitsAllowed: RequirementLevel = RequirementLevel.Optional)(using Context): Unit = (l, implicitsAllowed) match {
case ((p@(source.ValueParam(_, _, true, _) | source.BlockParam(_, _, true, _))) :: tl, RequirementLevel.Forbidden) =>
Context.at(p) {
Context.error(pretty"Implicit parameter ${l.head.span.text.getOrElse(l.head.id.name)} can never be passed implicitly to here.")
}
case ((p@(source.ValueParam(_, _, false, _) | source.BlockParam(_, _, false, _))) :: tl, RequirementLevel.Required) =>
Context.at(p) {
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.")
}
case ((source.ValueParam(_, _, true, _) | source.BlockParam(_, _, true, _)) :: tl, RequirementLevel.Optional) =>
checkImplicitParams(tl, RequirementLevel.Required) // require all arguments after an implicit one to be implicit
case (_ :: tl, _) =>
checkImplicitParams(tl, implicitsAllowed)
case (Nil, _) => ()
}

/**
* Name-resolves all implicit arguments that could be interesting for calling the given function symbol.
*
* Errors while doing so are residualized into the respective [[ImplicitContext]] and only reported
* during overload resolution later.
*/
def resolveImplicits(id: source.Id)(using Context): Unit = {
Context.symbolOf(id) match {
case symbols.CallTarget(syms, cs) =>
cs.foreach {
case (b, c@ImplicitContext(vimpls, bimpls)) if !c.resolved =>
c.resolved = true // set as resolved first, so recursive calls do not continue doing so.
c.values = vimpls.map { case k -> mv => k -> GenerateImplicitArgs.runPhaseOn(k, mv){ v => Try { resolve(v) } } }
c.blocks = bimpls.map { case k -> mb => k -> GenerateImplicitArgs.runPhaseOn(k, mb){ b => Try { resolve(b) } } }
case _ => ()
}
case _ => ()
}
}

def resolve(t: source.Term)(using Context): Unit = Context.focusing(t) {

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

case tree @ source.Region(name, body, _) =>
val regionName = Name.local(name.name)
val reg = BlockParam(regionName, Some(builtins.TRegion), CaptureParam(regionName), tree)
val reg = BlockParam(regionName, Some(builtins.TRegion), CaptureParam(regionName), isImplicit = false, tree)
Context.define(name, reg)
Context scoped {
Context.bindBlock(reg)
resolve(body)
}

case f @ source.BlockLiteral(tparams, vparams, bparams, stmt, _) =>
checkImplicitParams(vparams, RequirementLevel.Forbidden)
checkImplicitParams(bparams, RequirementLevel.Forbidden)
Context scoped {
val tps = tparams map resolve
val vps = vparams map resolve
Expand Down Expand Up @@ -551,19 +599,21 @@ object Namer extends Phase[Parsed, NameResolved] {
then Context.abort(pp"Cannot resolve function ${target}, called on an expression.")
}
}
resolveImplicits(target)
targs foreach resolveValueType
vargs foreach resolve
bargs foreach resolve

case source.Do(target, targs, vargs, bargs, _) =>
Context.resolveEffectCall(target)
resolveImplicits(target)
targs foreach resolveValueType
vargs foreach resolve
bargs foreach resolve

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

def resolve(p: source.ValueParam)(using Context): ValueParam = {
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = true)), decl = p)
val sym = ValueParam(Name.local(p.id), p.tpe.map(tpe => resolveValueType(tpe, isParam = true)), p.isImplicit, decl = p)
Context.assignSymbol(p.id, sym)
sym
}
def resolve(p: source.BlockParam)(using Context): BlockParam = {
val name = Name.local(p.id)
val sym: BlockParam = BlockParam(name, p.tpe.map { tpe => resolveBlockType(tpe, isParam = true) }, CaptureParam(name), p)
val sym: BlockParam = BlockParam(name, p.tpe.map { tpe => resolveBlockType(tpe, isParam = true) }, CaptureParam(name), p.isImplicit, p)
Context.assignSymbol(p.id, sym)
sym
}
Expand Down Expand Up @@ -719,7 +769,7 @@ object Namer extends Phase[Parsed, NameResolved] {
case source.IgnorePattern(_) => Nil
case source.LiteralPattern(lit, _) => Nil
case source.AnyPattern(id, _) =>
val p = ValueParam(Name.local(id), None, decl = id)
val p = ValueParam(Name.local(id), None, isImplicit = false, decl = id)
Context.assignSymbol(id, p)
List(p)
case source.TagPattern(id, patterns, _) =>
Expand Down Expand Up @@ -1081,7 +1131,11 @@ trait NamerOps extends ContextOps { Context: Context =>

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

if (syms2.nonEmpty) { assignSymbol(id, CallTarget(syms2.asInstanceOf)); true } else { false }
if (syms2.nonEmpty) {
val syms3 = syms2.asInstanceOf[List[Set[BlockSymbol]]]
assignSymbol(id, CallTarget(syms3, GenerateImplicitArgs.lookupPotentialImplicits(syms3, scope.scope)))
true
} else { false }
}

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

if (syms3.nonEmpty) { assignSymbol(id, CallTarget(syms3.asInstanceOf)); true } else { false }
if (syms3.nonEmpty) {
val syms4 = syms3.asInstanceOf[List[Set[BlockSymbol]]]
assignSymbol(id, CallTarget(syms4, GenerateImplicitArgs.lookupPotentialImplicits(syms4, scope.scope)))
true
} else { false }
}

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

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

assignSymbol(id, CallTarget(syms.asInstanceOf))
val bsyms = syms.asInstanceOf[List[Set[BlockSymbol]]]
assignSymbol(id, CallTarget(bsyms, GenerateImplicitArgs.lookupPotentialImplicits(bsyms, scope.scope)))
}

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

assignSymbol(id, CallTarget(syms.asInstanceOf))
val bsyms = syms.asInstanceOf[List[Set[BlockSymbol]]]
assignSymbol(id, CallTarget(bsyms, GenerateImplicitArgs.lookupPotentialImplicits(bsyms, scope.scope)))
}

/**
Expand Down
27 changes: 17 additions & 10 deletions effekt/shared/src/main/scala/effekt/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ class Parser(tokens: Seq[Token], source: Source) {
def peek(offset: Int, kind: TokenKind): Boolean =
peek(offset).kind == kind

def skipIf(kind: TokenKind): Boolean =
if (peek.kind == kind) { skip(); true } else { false }

def hasNext(): Boolean = position < tokens.length
def next(): Token =
val t = tokens(position).failOnErrorToken(position)
Expand Down Expand Up @@ -331,8 +334,8 @@ class Parser(tokens: Seq[Token], source: Source) {
// Simple case: all patterns are just variable names (or ignored), no guards, no fallback
// Desugar to: call { (x, y, _, ...) => body }
val vparams: List[ValueParam] = patterns.unspan.map {
case AnyPattern(id, span) => ValueParam(id, None, span)
case IgnorePattern(span) => ValueParam(IdDef(s"__ignored", span.synthesized), None, span)
case AnyPattern(id, span) => ValueParam(id, None, isImplicit = false, span)
case IgnorePattern(span) => ValueParam(IdDef(s"__ignored", span.synthesized), None, isImplicit = false, span)
case _ => sys.error("impossible: checked above")
}
BlockLiteral(Nil, vparams, Nil, body, body.span.synthesized)
Expand All @@ -345,7 +348,7 @@ class Parser(tokens: Seq[Token], source: Source) {
val argSpans = patternList.map(_.span)

val vparams: List[ValueParam] = names.zip(argSpans).map { (name, span) =>
ValueParam(IdDef(name, span.synthesized), None, span.synthesized)
ValueParam(IdDef(name, span.synthesized), None, isImplicit = false, span.synthesized)
}
val scrutinees = names.zip(argSpans).map { (name, span) =>
Var(IdRef(Nil, name, span.synthesized), span.synthesized)
Expand Down Expand Up @@ -931,7 +934,7 @@ class Parser(tokens: Seq[Token], source: Source) {
nonterminal:
`with` ~> backtrack(idDef() <~ `:`) ~ implementation() match {
case capabilityName ~ impl =>
val capability = capabilityName map { name => BlockParam(name, Some(impl.interface), name.span.synthesized): BlockParam }
val capability = capabilityName map { name => BlockParam(name, Some(impl.interface), isImplicit = false, name.span.synthesized): BlockParam }
Handler(capability.unspan, impl, span())
}

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

def lambdaParams(): (List[Id], List[ValueParam], List[BlockParam]) =
nonterminal:
if isVariable then (Nil, List(ValueParam(idDef(), None, span())), Nil) else paramsOpt()
if isVariable then (Nil, List(ValueParam(idDef(), None, isImplicit=false, span())), Nil) else paramsOpt()

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

def valueParam(): ValueParam =
nonterminal:
ValueParam(idDef(), Some(valueTypeAnnotation()), span())
val isImplicit = skipIf(`?`)
ValueParam(idDef(), Some(valueTypeAnnotation()), isImplicit, span())

def valueParamOpt(): ValueParam =
nonterminal:
ValueParam(idDef(), maybeValueTypeAnnotation(), span())
val isImplicit = skipIf(`?`)
ValueParam(idDef(), maybeValueTypeAnnotation(), isImplicit, span())

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

def blockParam(): BlockParam =
nonterminal:
BlockParam(idDef(), Some(blockTypeAnnotation()), span())
val isImplicit = skipIf(`?`)
BlockParam(idDef(), Some(blockTypeAnnotation()), isImplicit, span())

def blockParamOpt(): BlockParam =
nonterminal:
BlockParam(idDef(), when(`:`)(Some(blockType()))(None), span())
val isImplicit = skipIf(`?`)
BlockParam(idDef(), when(`:`)(Some(blockType()))(None), isImplicit, span())

def maybeValueTypes(): Many[Type] =
nonterminal:
Expand Down
Loading
Loading