-
Notifications
You must be signed in to change notification settings - Fork 40
Allow UFCS on (non-literal) block receivers #1250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Port parser changes allowing UFCS on block literals Co-authored-by: dvdvgt <david.voigt@mailbox.org>
|
This change might allow stuff that could be considered poor taste, such as |
|
This doesn't currently work well "in practice" for two reasons:
case m @ MethodCall(receiver, id, targs, vargs, bargs, span) =>
val vargsTransformed = vargs.map(rewriteAsExpr)
val bargsTransformed = bargs.map(rewriteAsBlock)
// Don't disambiguate here, instead let Typer handle it with full type information
// Just rewrite the receiver appropriately based on what candidates exist
val hasMethods = m.definition match {
case symbols.CallTarget(syms) => syms.flatten.exists(_.isInstanceOf[symbols.Operation])
case _: symbols.Operation => true
case _ => false
}
if (hasMethods) {
// Might be a true method call — receiver needs to be a block
MethodCall(rewriteAsBlock(receiver), id, targs, vargsTransformed, bargsTransformed, span)
} else {
// Pure UFCS — keep as MethodCall, Typer will decide value vs block UFCS
// Receiver stays as-is; Typer will rewrite appropriately based on types
MethodCall(receiver, id, targs, vargsTransformed, bargsTransformed, span)
}and then Typer: def checkOverloadedMethodCall(
call: source.CallLike,
receiver: source.Term,
id: source.IdRef,
targs: List[ValueType],
vargs: List[source.ValueArg],
bargs: List[source.Term],
expected: Option[ValueType]
)(using Context, Captures): Result[ValueType] = {
val sym = id.symbol
val (methods, functions) = sym match {
case CallTarget(syms) =>
val all = syms.flatten
(all.collect { case op: Operation => op },
all.collect { case fn: Callable => fn })
case sym: Operation => (List(sym), Nil)
case sym: Callable => (Nil, List(sym))
case s => Context.panic(s"Not a valid method/function: ${s}")
}
// Try 1: True method calls (receiver is interface instance)
val methodResults = if (methods.nonEmpty) {
val Result(recvTpe, recvEffs) = checkExprAsBlock(receiver, None)
// ... existing method resolution logic
} else Nil
// Try 2: Value-UFCS (receiver as first value arg)
val valueUFCSResults = tryEach(functions.filter(_.vparams.nonEmpty)) { fn =>
val Result(recvTpe, recvEffs) = checkExpr(receiver, Some(fn.vparams.head.tpe.get))
checkCallTo(call, fn.name.name, ..., ValueArg.Unnamed(receiver) :: vargs, bargs, expected)
}
// Try 3: Block-UFCS (receiver as first block arg)
val blockUFCSResults = tryEach(functions.filter(_.bparams.nonEmpty)) { fn =>
val Result(recvTpe, recvEffs) = checkExprAsBlock(receiver, Some(fn.bparams.head.tpe.get))
checkCallTo(call, fn.name.name, ..., vargs, receiver :: bargs, expected)
}
// Combine and resolve
resolveOverload(id, List(methodResults, valueUFCSResults, blockUFCSResults), allErrors)
} |
This comment was marked as resolved.
This comment was marked as resolved.
|
This was uncontroversial on the Effekt Working Group meeting and doesn't interfere with anyone else's work, so I'll just merge it :) |
Resolves #1186
Supersedes #1046
foo.bar(baz) { quux }is expanded by UnboxInference into:baronfoo(ifbaris a known operation call)bar(foo, baz) { quux }bar(baz) { foo } { quux }ifffoois syntactically not a block literalSee the two added examples, no idea if this breaks any code.