Skip to content

Commit d673b97

Browse files
authored
Merge pull request #14816 from dotty-staging/fix-14783
Fix parameter untupling
2 parents 63da64f + 0a95a3d commit d673b97

File tree

9 files changed

+100
-20
lines changed

9 files changed

+100
-20
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ object desugar {
3939
*/
4040
val MultiLineInfix: Property.Key[Unit] = Property.StickyKey()
4141

42+
/** An attachment key to indicate that a ValDef originated from parameter untupling.
43+
*/
44+
val UntupledParam: Property.Key[Unit] = Property.StickyKey()
45+
4246
/** What static check should be applied to a Match? */
4347
enum MatchCheck {
4448
case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom
@@ -1426,7 +1430,9 @@ object desugar {
14261430
val vdefs =
14271431
params.zipWithIndex.map {
14281432
case (param, idx) =>
1429-
DefDef(param.name, Nil, param.tpt, selector(idx)).withSpan(param.span)
1433+
ValDef(param.name, param.tpt, selector(idx))
1434+
.withSpan(param.span)
1435+
.withAttachment(UntupledParam, ())
14301436
}
14311437
Function(param :: Nil, Block(vdefs, body))
14321438
}

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -404,18 +404,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
404404
}
405405

406406
/** A tree representing the same reference as the given type */
407-
def ref(tp: NamedType)(using Context): Tree =
407+
def ref(tp: NamedType, needLoad: Boolean = true)(using Context): Tree =
408408
if (tp.isType) TypeTree(tp)
409409
else if (prefixIsElidable(tp)) Ident(tp)
410410
else if (tp.symbol.is(Module) && ctx.owner.isContainedIn(tp.symbol.moduleClass))
411411
followOuterLinks(This(tp.symbol.moduleClass.asClass))
412412
else if (tp.symbol hasAnnotation defn.ScalaStaticAnnot)
413413
Ident(tp)
414-
else {
414+
else
415415
val pre = tp.prefix
416-
if (pre.isSingleton) followOuterLinks(singleton(pre.dealias)).select(tp)
417-
else Select(TypeTree(pre), tp)
418-
}
416+
if (pre.isSingleton) followOuterLinks(singleton(pre.dealias, needLoad)).select(tp)
417+
else
418+
val res = Select(TypeTree(pre), tp)
419+
if needLoad && !res.symbol.isStatic then
420+
throw new TypeError(em"cannot establish a reference to $res")
421+
res
419422

420423
def ref(sym: Symbol)(using Context): Tree =
421424
ref(NamedType(sym.owner.thisType, sym.name, sym.denot))
@@ -428,11 +431,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
428431
t
429432
}
430433

431-
def singleton(tp: Type)(using Context): Tree = tp.dealias match {
432-
case tp: TermRef => ref(tp)
434+
def singleton(tp: Type, needLoad: Boolean = true)(using Context): Tree = tp.dealias match {
435+
case tp: TermRef => ref(tp, needLoad)
433436
case tp: ThisType => This(tp.cls)
434-
case tp: SkolemType => singleton(tp.narrow)
435-
case SuperType(qual, _) => singleton(qual)
437+
case tp: SkolemType => singleton(tp.narrow, needLoad)
438+
case SuperType(qual, _) => singleton(qual, needLoad)
436439
case ConstantType(value) => Literal(value)
437440
}
438441

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ object Completion {
8585
* @param content The source content that we'll check the positions for the prefix
8686
* @param start The start position we'll start to look for the prefix at
8787
* @param end The end position we'll look for the prefix at
88-
* @return Either the full prefix including the ` or an empty string
88+
* @return Either the full prefix including the ` or an empty string
8989
*/
90-
private def checkBacktickPrefix(content: Array[Char], start: Int, end: Int): String =
90+
private def checkBacktickPrefix(content: Array[Char], start: Int, end: Int): String =
9191
content.lift(start) match
9292
case Some(char) if char == '`' =>
9393
content.slice(start, end).mkString
@@ -111,7 +111,7 @@ object Completion {
111111
// Foo.`se<TAB> will result in Select(Ident(Foo), <error>)
112112
case (select: untpd.Select) :: _ if select.name == nme.ERROR =>
113113
checkBacktickPrefix(select.source.content(), select.nameSpan.start, select.span.end)
114-
114+
115115
// import scala.util.chaining.`s<TAB> will result in a Ident(<error>)
116116
case (ident: untpd.Ident) :: _ if ident.name == nme.ERROR =>
117117
checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end)
@@ -177,14 +177,14 @@ object Completion {
177177
// https://github.com/com-lihaoyi/Ammonite/blob/73a874173cd337f953a3edc9fb8cb96556638fdd/amm/util/src/main/scala/ammonite/util/Model.scala
178178
private def needsBacktick(s: String) =
179179
val chunks = s.split("_", -1)
180-
180+
181181
val validChunks = chunks.zipWithIndex.forall { case (chunk, index) =>
182182
chunk.forall(Chars.isIdentifierPart) ||
183183
(chunk.forall(Chars.isOperatorPart) &&
184184
index == chunks.length - 1 &&
185185
!(chunks.lift(index - 1).contains("") && index - 1 == 0))
186186
}
187-
187+
188188
val validStart =
189189
Chars.isIdentifierStart(s(0)) || chunks(0).forall(Chars.isOperatorPart)
190190

@@ -312,7 +312,7 @@ object Completion {
312312

313313
/** Replaces underlying type with reduced one, when it's MatchType */
314314
def reduceUnderlyingMatchType(qual: Tree)(using Context): Tree=
315-
qual.tpe.widen match
315+
qual.tpe.widen match
316316
case ctx.typer.MatchTypeInDisguise(mt) => qual.withType(mt)
317317
case _ => qual
318318

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dotty.tools.dotc
22
package transform
33

4-
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
4+
import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar}
55
import scala.collection.mutable
66
import core._
77
import dotty.tools.dotc.typer.Checking
@@ -255,6 +255,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
255255
if tree.symbol.is(ConstructorProxy) then
256256
report.error(em"constructor proxy ${tree.symbol} cannot be used as a value", tree.srcPos)
257257

258+
def checkStableSelection(tree: Tree)(using Context): Unit =
259+
def check(qual: Tree) =
260+
if !qual.tpe.isStable then
261+
report.error(em"Parameter untupling cannot be used for call-by-name parameters", tree.srcPos)
262+
tree match
263+
case Select(qual, _) => check(qual) // simple select _n
264+
case Apply(TypeApply(Select(qual, _), _), _) => check(qual) // generic select .apply[T](n)
265+
258266
override def transform(tree: Tree)(using Context): Tree =
259267
try tree match {
260268
// TODO move CaseDef case lower: keep most probable trees first for performance
@@ -356,6 +364,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
356364
case tree: ValDef =>
357365
checkErasedDef(tree)
358366
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
367+
if tree1.removeAttachment(desugar.UntupledParam).isDefined then
368+
checkStableSelection(tree.rhs)
359369
processValOrDefDef(super.transform(tree1))
360370
case tree: DefDef =>
361371
checkErasedDef(tree)

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ trait Applications extends Compatibility {
705705
def fail(msg: Message): Unit =
706706
ok = false
707707
def appPos: SrcPos = NoSourcePosition
708-
@threadUnsafe lazy val normalizedFun: Tree = ref(methRef)
708+
@threadUnsafe lazy val normalizedFun: Tree = ref(methRef, needLoad = false)
709709
init()
710710
}
711711

@@ -2268,7 +2268,7 @@ trait Applications extends Compatibility {
22682268
case TypeApply(fun, args) => TypeApply(replaceCallee(fun, replacement), args)
22692269
case _ => replacement
22702270

2271-
val methodRefTree = ref(methodRef)
2271+
val methodRefTree = ref(methodRef, needLoad = false)
22722272
val truncatedSym = methodRef.symbol.asTerm.copy(info = truncateExtension(methodRef.info))
22732273
val truncatedRefTree = untpd.TypedSplice(ref(truncatedSym)).withSpan(receiver.span)
22742274
val newCtx = ctx.fresh.setNewScope.setReporter(new reporting.ThrowingReporter(ctx.reporter))

tests/neg/function-arity-2.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test {
2+
3+
class T[A] { def foo(f: (=> A) => Int) = f(???) }
4+
5+
def main(args: Array[String]): Unit =
6+
new T[(Int, Int)].foo((x, y) => 0) // error // error Parameter untupling cannot be used for call-by-name parameters (twice)
7+
}

tests/neg/i14783.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Test:
2+
def foo(f: (=> (Int, Int)) => Int) = ???
3+
foo((a, b) => a + b) // error // error

tests/pos/i14783.scala

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
object Wart:
2+
def bar(using c: Ctx)(ws: List[Wrap[c.type]]): Unit =
3+
ws.zipWithIndex.foreach { (w, _) => w.x.foo }
4+
5+
trait Wrap[C <: Ctx & Singleton]:
6+
val ctx: C
7+
def x: ctx.inner.X
8+
9+
trait Ctx:
10+
object inner:
11+
type X
12+
extension (self: X) def foo: Int = ???
13+
14+
15+
object WartInspector:
16+
def myWartTraverser: WartTraverser = ???
17+
def inspect(using q: Quotes)(tastys: List[Tasty[q.type]]): Unit = {
18+
val universe: WartUniverse.Aux[q.type] = WartUniverse(q)
19+
val traverser = myWartTraverser.get(universe)
20+
tastys.zipWithIndex.foreach { (tasty, index) =>
21+
val tree = tasty.ast
22+
traverser.traverseTree(tree)(tree.symbol)
23+
}
24+
}
25+
26+
object WartUniverse:
27+
type Aux[X <: Quotes] = WartUniverse { type Q = X }
28+
def apply[Q <: Quotes](quotes: Q): Aux[Q] = ???
29+
30+
31+
abstract class WartUniverse:
32+
type Q <: Quotes
33+
val quotes: Q
34+
abstract class Traverser extends quotes.reflect.TreeTraverser
35+
36+
37+
abstract class WartTraverser:
38+
def get(u: WartUniverse): u.Traverser
39+
40+
trait Tasty[Q <: Quotes & Singleton]:
41+
val quotes: Q
42+
def path: String
43+
def ast: quotes.reflect.Tree
44+
45+
trait Quotes:
46+
object reflect:
47+
type Tree
48+
extension (self: Tree) def symbol: Symbol = ???
49+
type Symbol
50+
trait TreeTraverser:
51+
def traverseTree(tree: Tree)(symbol: Symbol): Unit

tests/run/function-arity.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ object Test {
33

44
def main(args: Array[String]): Unit = {
55
new T[(Int, Int)].foo((ii) => 0)
6-
new T[(Int, Int)].foo((x, y) => 0) // check that this does not run into ???
6+
//new T[(Int, Int)].foo((x, y) => 0) // not allowed anymore
77
}
88
}

0 commit comments

Comments
 (0)