Skip to content

Commit cd76d65

Browse files
committed
Disallow direct unqualified mentions of extension methods.
Allow only qualified references `p.f` to an extension method `f`.
1 parent 312a420 commit cd76d65

File tree

16 files changed

+111
-81
lines changed

16 files changed

+111
-81
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
2424
}
2525

2626
/** A typed subtree of an untyped tree needs to be wrapped in a TypedSplice
27-
* @param owner The current owner at the time the tree was defined
27+
* @param owner The current owner at the time the tree was defined
28+
* @param isExtensionReceiver The splice was created from the receiver `e` in an extension
29+
* method call `e.f(...)`
2830
*/
29-
abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol)(implicit @constructorOnly src: SourceFile) extends ProxyTree {
31+
abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol, val isExtensionReceiver: Boolean)(implicit @constructorOnly src: SourceFile) extends ProxyTree {
3032
def forwardTo: tpd.Tree = splice
33+
override def toString =
34+
def ext = if isExtensionReceiver then ", isExtensionReceiver = true" else ""
35+
s"TypedSplice($splice$ext)"
3136
}
3237

3338
object TypedSplice {
34-
def apply(tree: tpd.Tree)(using Context): TypedSplice =
35-
new TypedSplice(tree)(ctx.owner) {}
39+
def apply(tree: tpd.Tree, isExtensionReceiver: Boolean = false)(using Context): TypedSplice =
40+
new TypedSplice(tree)(ctx.owner, isExtensionReceiver) {}
3641
}
3742

3843
/** mods object name impl */

compiler/src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,11 @@ object Denotations {
257257
def checkUnique(using Context): SingleDenotation = suchThat(alwaysTrue)
258258

259259
/** Does this denotation have an alternative that satisfies the predicate `p`? */
260-
def hasAltWith(p: SingleDenotation => Boolean): Boolean
260+
inline def hasAltWith(inline p: SingleDenotation => Boolean): Boolean =
261+
def p1(x: SingleDenotation) = p(x)
262+
this match
263+
case d: SingleDenotation => exists && p1(d)
264+
case _ => filterWithPredicate(p1).exists
261265

262266
/** The denotation made up from the alternatives of this denotation that
263267
* are accessible from prefix `pre`, or NoDenotation if no accessible alternative exists.
@@ -604,9 +608,6 @@ object Denotations {
604608
def suchThat(p: Symbol => Boolean)(using Context): SingleDenotation =
605609
if (exists && p(symbol)) this else NoDenotation
606610

607-
def hasAltWith(p: SingleDenotation => Boolean): Boolean =
608-
exists && p(this)
609-
610611
def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation =
611612
if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation
612613

@@ -1185,8 +1186,6 @@ object Denotations {
11851186
}
11861187
override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation =
11871188
derivedUnionDenotation(denot1.filterWithPredicate(p), denot2.filterWithPredicate(p))
1188-
def hasAltWith(p: SingleDenotation => Boolean): Boolean =
1189-
denot1.hasAltWith(p) || denot2.hasAltWith(p)
11901189
def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation = {
11911190
val d1 = denot1 accessibleFrom (pre, superAccess)
11921191
val d2 = denot2 accessibleFrom (pre, superAccess)

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2162,7 +2162,9 @@ trait Applications extends Compatibility {
21622162

21632163
val (core, pt1) = normalizePt(methodRef, pt)
21642164
val app = withMode(Mode.SynthesizeExtMethodReceiver) {
2165-
typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)
2165+
typed(
2166+
untpd.Apply(core, untpd.TypedSplice(receiver, isExtensionReceiver = true) :: Nil),
2167+
pt1, ctx.typerState.ownedVars)
21662168
}
21672169
def isExtension(tree: Tree): Boolean = methPart(tree) match {
21682170
case Inlined(call, _, _) => isExtension(call)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,12 @@ object ProtoTypes {
517517
/** A prototype for type constructors that are followed by a type application */
518518
@sharable object AnyTypeConstructorProto extends UncachedGroundType with MatchAlways
519519

520+
extension (pt: Type)
521+
def isExtensionApplyProto: Boolean = pt match
522+
case PolyProto(targs, res) => res.isExtensionApplyProto
523+
case FunProto((arg: untpd.TypedSplice) :: Nil, _) => arg.isExtensionReceiver
524+
case _ => false
525+
520526
/** Add all parameters of given type lambda `tl` to the constraint's domain.
521527
* If the constraint contains already some of these parameters in its domain,
522528
* make a copy of the type lambda and add the copy's type parameters instead.

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -450,27 +450,30 @@ class Typer extends Namer
450450
if (name == nme.ROOTPKG)
451451
return tree.withType(defn.RootPackage.termRef)
452452

453-
val rawType = {
453+
val rawType =
454454
val saved1 = unimported
455455
val saved2 = foundUnderScala2
456456
unimported = Set.empty
457457
foundUnderScala2 = NoType
458-
try {
459-
var found = findRef(name, pt, EmptyFlags, tree.srcPos)
460-
if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) {
458+
try
459+
val found = findRef(name, pt, EmptyFlags, tree.srcPos)
460+
if foundUnderScala2.exists && !(foundUnderScala2 =:= found) then
461461
report.migrationWarning(
462462
ex"""Name resolution will change.
463463
| currently selected : $foundUnderScala2
464464
| in the future, without -source 3.0-migration: $found""", tree.srcPos)
465-
found = foundUnderScala2
466-
}
467-
found
468-
}
469-
finally {
465+
foundUnderScala2
466+
else
467+
found match
468+
case found: TermRef
469+
if !found.denot.hasAltWith(!_.symbol.is(ExtensionMethod))
470+
&& !pt.isExtensionApplyProto =>
471+
NoType // direct calls to extension methods need a prefix
472+
case _ =>
473+
found
474+
finally
470475
unimported = saved1
471476
foundUnderScala2 = saved2
472-
}
473-
}
474477

475478
def setType(ownType: Type): Tree =
476479
val tree1 = ownType match
@@ -842,7 +845,9 @@ class Typer extends Namer
842845
case fn @ TypeApply(fn1, targs) =>
843846
untpd.cpy.TypeApply(fn)(toSetter(fn1), targs.map(untpd.TypedSplice(_)))
844847
case fn @ Apply(fn1, args) =>
845-
val result = untpd.cpy.Apply(fn)(toSetter(fn1), args.map(untpd.TypedSplice(_)))
848+
val result = untpd.cpy.Apply(fn)(
849+
toSetter(fn1),
850+
args.map(untpd.TypedSplice(_, isExtensionReceiver = true)))
846851
fn1 match
847852
case Apply(_, _) => // current apply is to implicit arguments
848853
result.setApplyKind(ApplyKind.Using)
@@ -2947,7 +2952,15 @@ class Typer extends Namer
29472952
}
29482953

29492954
def adaptOverloaded(ref: TermRef) = {
2950-
val altDenots = ref.denot.alternatives
2955+
val altDenots =
2956+
val allDenots = ref.denot.alternatives
2957+
def isIdent = tree match
2958+
case _: Ident => true
2959+
case Select(qual, name) => qual.span.isZeroExtent
2960+
case _ => false
2961+
if pt.isExtensionApplyProto then allDenots.filter(_.symbol.is(ExtensionMethod))
2962+
else if isIdent then allDenots.filterNot(_.symbol.is(ExtensionMethod))
2963+
else allDenots
29512964
typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")
29522965
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
29532966
val alts = altDenots.map(altRef)

tests/neg/i6183.check

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
-- [E008] Not Found Error: tests/neg/i6183.scala:5:5 -------------------------------------------------------------------
2-
5 | 42.render // error
3-
| ^^^^^^^^^
4-
| value render is not a member of Int.
5-
| An extension method was tried, but could not be fully constructed:
1+
-- [E008] Not Found Error: tests/neg/i6183.scala:6:7 -------------------------------------------------------------------
2+
6 | 42.render // error
3+
| ^^^^^^^^^
4+
| value render is not a member of Int.
5+
| An extension method was tried, but could not be fully constructed:
66
|
7-
| extension_render(42)
8-
-- [E051] Reference Error: tests/neg/i6183.scala:6:2 -------------------------------------------------------------------
9-
6 | extension_render(42) // error
10-
| ^^^^^^^^^^^^^^^^
11-
| Ambiguous overload. The overloaded alternatives of method extension_render with types
12-
| [B](b: B)(using x$1: DummyImplicit): Char
13-
| [A](a: A): String
14-
| both match arguments ((42 : Int))
7+
| extension_render(42)
8+
-- [E051] Reference Error: tests/neg/i6183.scala:7:9 -------------------------------------------------------------------
9+
7 | Test.extension_render(42) // error
10+
| ^^^^^^^^^^^^^^^^^^^^^
11+
| Ambiguous overload. The overloaded alternatives of method extension_render in object Test with types
12+
| [B](b: B)(using x$1: DummyImplicit): Char
13+
| [A](a: A): String
14+
| both match arguments ((42 : Int))
1515

1616
longer explanation available when compiling with `-explain`

tests/neg/i6183.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
extension [A](a: A) def render: String = "Hi"
2-
extension [B](b: B) def render(using DummyImplicit): Char = 'x'
1+
object Test:
2+
extension [A](a: A) def render: String = "Hi"
3+
extension [B](b: B) def render(using DummyImplicit): Char = 'x'
34

4-
val test = {
5-
42.render // error
6-
extension_render(42) // error
7-
}
5+
val test = {
6+
42.render // error
7+
Test.extension_render(42) // error
8+
}

tests/neg/i6779.check

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:9:30 --------------------------------------------------------------
2-
9 |def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
3-
| ^^^^^^^^^^^^^^^^^^^^^^^^
4-
| Found: F[T]
5-
| Required: F[G[T]]
6-
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:11:29 -------------------------------------------------------------
7-
11 |def g2[T](x: T): F[G[T]] = x.f // error
8-
| ^^^
9-
| Found: F[T]
10-
| Required: F[G[T]]
11-
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:13:41 -------------------------------------------------------------
12-
13 |def g3[T](x: T): F[G[T]] = extension_f(x)(using summon[Stuff]) // error
13-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14-
| Found: F[T]
15-
| Required: F[G[T]]
1+
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:10:32 -------------------------------------------------------------
2+
10 | def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: F[T]
5+
| Required: F[G[T]]
6+
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:12:31 -------------------------------------------------------------
7+
12 | def g2[T](x: T): F[G[T]] = x.f // error
8+
| ^^^
9+
| Found: F[T]
10+
| Required: F[G[T]]
11+
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:14:48 -------------------------------------------------------------
12+
14 | def g3[T](x: T): F[G[T]] = this.extension_f(x)(using summon[Stuff]) // error
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
| Found: F[T]
15+
| Required: F[G[T]]

tests/neg/i6779.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ type G[T]
33
type Stuff
44
given Stuff = ???
55

6-
extension [T](x: T) def f(using Stuff): F[T] = ???
6+
object Test:
7+
extension [T](x: T) def f(using Stuff): F[T] = ???
78

89

9-
def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
10+
def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
1011

11-
def g2[T](x: T): F[G[T]] = x.f // error
12+
def g2[T](x: T): F[G[T]] = x.f // error
1213

13-
def g3[T](x: T): F[G[T]] = extension_f(x)(using summon[Stuff]) // error
14+
def g3[T](x: T): F[G[T]] = this.extension_f(x)(using summon[Stuff]) // error

tests/neg/i9185.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
| M.extension_pure[A, F]("ola")(
88
| /* ambiguous: both object listMonad in object M and object optionMonad in object M match type M[F] */summon[M[F]]
99
| )
10-
-- Error: tests/neg/i9185.scala:8:36 -----------------------------------------------------------------------------------
11-
8 | val value3 = extension_pure("ola") // error
12-
| ^
10+
-- Error: tests/neg/i9185.scala:8:38 -----------------------------------------------------------------------------------
11+
8 | val value3 = M.extension_pure("ola") // error
12+
| ^
1313
|ambiguous implicit arguments: both object listMonad in object M and object optionMonad in object M match type M[F] of parameter m of method extension_pure in object M
1414
-- [E008] Not Found Error: tests/neg/i9185.scala:11:16 -----------------------------------------------------------------
1515
11 | val l = "abc".len // error

0 commit comments

Comments
 (0)