Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
Disallow direct unqualified mentions of extension methods.
Allow only qualified references `p.f` to an extension method `f`.
  • Loading branch information
odersky committed Nov 7, 2020
commit cd76d65fade8d3a0824291e4e42b0b7b04569556
13 changes: 9 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
}

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

object TypedSplice {
def apply(tree: tpd.Tree)(using Context): TypedSplice =
new TypedSplice(tree)(ctx.owner) {}
def apply(tree: tpd.Tree, isExtensionReceiver: Boolean = false)(using Context): TypedSplice =
new TypedSplice(tree)(ctx.owner, isExtensionReceiver) {}
}

/** mods object name impl */
Expand Down
11 changes: 5 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,11 @@ object Denotations {
def checkUnique(using Context): SingleDenotation = suchThat(alwaysTrue)

/** Does this denotation have an alternative that satisfies the predicate `p`? */
def hasAltWith(p: SingleDenotation => Boolean): Boolean
inline def hasAltWith(inline p: SingleDenotation => Boolean): Boolean =
def p1(x: SingleDenotation) = p(x)
this match
case d: SingleDenotation => exists && p1(d)
case _ => filterWithPredicate(p1).exists

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

def hasAltWith(p: SingleDenotation => Boolean): Boolean =
exists && p(this)

def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation =
if (!symbol.exists || symbol.isAccessibleFrom(pre, superAccess)) this else NoDenotation

Expand Down Expand Up @@ -1185,8 +1186,6 @@ object Denotations {
}
override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation =
derivedUnionDenotation(denot1.filterWithPredicate(p), denot2.filterWithPredicate(p))
def hasAltWith(p: SingleDenotation => Boolean): Boolean =
denot1.hasAltWith(p) || denot2.hasAltWith(p)
def accessibleFrom(pre: Type, superAccess: Boolean)(using Context): Denotation = {
val d1 = denot1 accessibleFrom (pre, superAccess)
val d2 = denot2 accessibleFrom (pre, superAccess)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2162,7 +2162,9 @@ trait Applications extends Compatibility {

val (core, pt1) = normalizePt(methodRef, pt)
val app = withMode(Mode.SynthesizeExtMethodReceiver) {
typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)
typed(
untpd.Apply(core, untpd.TypedSplice(receiver, isExtensionReceiver = true) :: Nil),
pt1, ctx.typerState.ownedVars)
}
def isExtension(tree: Tree): Boolean = methPart(tree) match {
case Inlined(call, _, _) => isExtension(call)
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,12 @@ object ProtoTypes {
/** A prototype for type constructors that are followed by a type application */
@sharable object AnyTypeConstructorProto extends UncachedGroundType with MatchAlways

extension (pt: Type)
def isExtensionApplyProto: Boolean = pt match
case PolyProto(targs, res) => res.isExtensionApplyProto
case FunProto((arg: untpd.TypedSplice) :: Nil, _) => arg.isExtensionReceiver
case _ => false

/** Add all parameters of given type lambda `tl` to the constraint's domain.
* If the constraint contains already some of these parameters in its domain,
* make a copy of the type lambda and add the copy's type parameters instead.
Expand Down
39 changes: 26 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -450,27 +450,30 @@ class Typer extends Namer
if (name == nme.ROOTPKG)
return tree.withType(defn.RootPackage.termRef)

val rawType = {
val rawType =
val saved1 = unimported
val saved2 = foundUnderScala2
unimported = Set.empty
foundUnderScala2 = NoType
try {
var found = findRef(name, pt, EmptyFlags, tree.srcPos)
if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) {
try
val found = findRef(name, pt, EmptyFlags, tree.srcPos)
if foundUnderScala2.exists && !(foundUnderScala2 =:= found) then
report.migrationWarning(
ex"""Name resolution will change.
| currently selected : $foundUnderScala2
| in the future, without -source 3.0-migration: $found""", tree.srcPos)
found = foundUnderScala2
}
found
}
finally {
foundUnderScala2
else
found match
case found: TermRef
if !found.denot.hasAltWith(!_.symbol.is(ExtensionMethod))
&& !pt.isExtensionApplyProto =>
NoType // direct calls to extension methods need a prefix
case _ =>
found
finally
unimported = saved1
foundUnderScala2 = saved2
}
}

def setType(ownType: Type): Tree =
val tree1 = ownType match
Expand Down Expand Up @@ -842,7 +845,9 @@ class Typer extends Namer
case fn @ TypeApply(fn1, targs) =>
untpd.cpy.TypeApply(fn)(toSetter(fn1), targs.map(untpd.TypedSplice(_)))
case fn @ Apply(fn1, args) =>
val result = untpd.cpy.Apply(fn)(toSetter(fn1), args.map(untpd.TypedSplice(_)))
val result = untpd.cpy.Apply(fn)(
toSetter(fn1),
args.map(untpd.TypedSplice(_, isExtensionReceiver = true)))
fn1 match
case Apply(_, _) => // current apply is to implicit arguments
result.setApplyKind(ApplyKind.Using)
Expand Down Expand Up @@ -2947,7 +2952,15 @@ class Typer extends Namer
}

def adaptOverloaded(ref: TermRef) = {
val altDenots = ref.denot.alternatives
val altDenots =
val allDenots = ref.denot.alternatives
def isIdent = tree match
case _: Ident => true
case Select(qual, name) => qual.span.isZeroExtent
case _ => false
if pt.isExtensionApplyProto then allDenots.filter(_.symbol.is(ExtensionMethod))
else if isIdent then allDenots.filterNot(_.symbol.is(ExtensionMethod))
else allDenots
typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%\n\n %")
def altRef(alt: SingleDenotation) = TermRef(ref.prefix, ref.name, alt)
val alts = altDenots.map(altRef)
Expand Down
26 changes: 13 additions & 13 deletions tests/neg/i6183.check
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
-- [E008] Not Found Error: tests/neg/i6183.scala:5:5 -------------------------------------------------------------------
5 | 42.render // error
| ^^^^^^^^^
| value render is not a member of Int.
| An extension method was tried, but could not be fully constructed:
-- [E008] Not Found Error: tests/neg/i6183.scala:6:7 -------------------------------------------------------------------
6 | 42.render // error
| ^^^^^^^^^
| value render is not a member of Int.
| An extension method was tried, but could not be fully constructed:
|
| extension_render(42)
-- [E051] Reference Error: tests/neg/i6183.scala:6:2 -------------------------------------------------------------------
6 | extension_render(42) // error
| ^^^^^^^^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of method extension_render with types
| [B](b: B)(using x$1: DummyImplicit): Char
| [A](a: A): String
| both match arguments ((42 : Int))
| extension_render(42)
-- [E051] Reference Error: tests/neg/i6183.scala:7:9 -------------------------------------------------------------------
7 | Test.extension_render(42) // error
| ^^^^^^^^^^^^^^^^^^^^^
| Ambiguous overload. The overloaded alternatives of method extension_render in object Test with types
| [B](b: B)(using x$1: DummyImplicit): Char
| [A](a: A): String
| both match arguments ((42 : Int))

longer explanation available when compiling with `-explain`
13 changes: 7 additions & 6 deletions tests/neg/i6183.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
extension [A](a: A) def render: String = "Hi"
extension [B](b: B) def render(using DummyImplicit): Char = 'x'
object Test:
extension [A](a: A) def render: String = "Hi"
extension [B](b: B) def render(using DummyImplicit): Char = 'x'

val test = {
42.render // error
extension_render(42) // error
}
val test = {
42.render // error
Test.extension_render(42) // error
}
30 changes: 15 additions & 15 deletions tests/neg/i6779.check
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:9:30 --------------------------------------------------------------
9 |def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^
| Found: F[T]
| Required: F[G[T]]
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:11:29 -------------------------------------------------------------
11 |def g2[T](x: T): F[G[T]] = x.f // error
| ^^^
| Found: F[T]
| Required: F[G[T]]
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:13:41 -------------------------------------------------------------
13 |def g3[T](x: T): F[G[T]] = extension_f(x)(using summon[Stuff]) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: F[T]
| Required: F[G[T]]
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:10:32 -------------------------------------------------------------
10 | def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^
| Found: F[T]
| Required: F[G[T]]
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:12:31 -------------------------------------------------------------
12 | def g2[T](x: T): F[G[T]] = x.f // error
| ^^^
| Found: F[T]
| Required: F[G[T]]
-- [E007] Type Mismatch Error: tests/neg/i6779.scala:14:48 -------------------------------------------------------------
14 | def g3[T](x: T): F[G[T]] = this.extension_f(x)(using summon[Stuff]) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: F[T]
| Required: F[G[T]]
9 changes: 5 additions & 4 deletions tests/neg/i6779.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ type G[T]
type Stuff
given Stuff = ???

extension [T](x: T) def f(using Stuff): F[T] = ???
object Test:
extension [T](x: T) def f(using Stuff): F[T] = ???


def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error
def g1[T](x: T): F[G[T]] = x.f(using summon[Stuff]) // error

def g2[T](x: T): F[G[T]] = x.f // error
def g2[T](x: T): F[G[T]] = x.f // error

def g3[T](x: T): F[G[T]] = extension_f(x)(using summon[Stuff]) // error
def g3[T](x: T): F[G[T]] = this.extension_f(x)(using summon[Stuff]) // error
6 changes: 3 additions & 3 deletions tests/neg/i9185.check
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
| M.extension_pure[A, F]("ola")(
| /* ambiguous: both object listMonad in object M and object optionMonad in object M match type M[F] */summon[M[F]]
| )
-- Error: tests/neg/i9185.scala:8:36 -----------------------------------------------------------------------------------
8 | val value3 = extension_pure("ola") // error
| ^
-- Error: tests/neg/i9185.scala:8:38 -----------------------------------------------------------------------------------
8 | val value3 = M.extension_pure("ola") // error
| ^
|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
-- [E008] Not Found Error: tests/neg/i9185.scala:11:16 -----------------------------------------------------------------
11 | val l = "abc".len // error
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i9185.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object M {
given optionMonad as M[Option] { def pure[A](x: A): Option[A] = Some(x) }
val value1: List[String] = "ola".pure
val value2 = "ola".pure // error
val value3 = extension_pure("ola") // error
val value3 = M.extension_pure("ola") // error

extension (x: Int) def len: Int = x
val l = "abc".len // error
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i7401.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ object Test {
x.foo(4)
x.foo
Test.extension_foo(x)(4)
extension_foo(x)
this.extension_foo(x)
}
4 changes: 2 additions & 2 deletions tests/pos/reference/extension-methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ object ExtMethods:

val circle = Circle(0, 0, 1)
circle.circumference
assert(circle.circumference == extension_circumference(circle))
assert(circle.circumference == this.extension_circumference(circle))

extension (x: String) def < (y: String) = x.compareTo(y) < 0
extension [Elem](x: Elem) def #: (xs: Seq[Elem]) = x +: xs
Expand Down Expand Up @@ -118,6 +118,6 @@ object ExtMethods:
if exponent == 0 then 1 else x * (x ** (exponent - 1))

import DoubleOps.{**, extension_**}
assert(2.0 ** 3 == extension_**(2.0)(3))
assert(2.0 ** 3 == DoubleOps.extension_**(2.0)(3))

end ExtMethods
18 changes: 10 additions & 8 deletions tests/run-macros/i6201/macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import scala.quoted._

extension (inline x: String) inline def strip: String =
${ stripImpl('x) }
object M:

def stripImpl(x: Expr[String])(using qctx: QuoteContext) : Expr[String] =
Expr(x.unliftOrError.stripMargin)
extension (inline x: String) inline def strip: String =
${ stripImpl('x) }

inline def isHello(inline x: String): Boolean =
${ isHelloImpl('x) }
def stripImpl(x: Expr[String])(using qctx: QuoteContext) : Expr[String] =
Expr(x.unliftOrError.stripMargin)

def isHelloImpl(x: Expr[String])(using qctx: QuoteContext) : Expr[Boolean] =
if (x.unliftOrError == "hello") Expr(true) else Expr(false)
inline def isHello(inline x: String): Boolean =
${ isHelloImpl('x) }

def isHelloImpl(x: Expr[String])(using qctx: QuoteContext) : Expr[Boolean] =
if (x.unliftOrError == "hello") Expr(true) else Expr(false)
5 changes: 3 additions & 2 deletions tests/run-macros/i6201/test_2.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
object Test {
import M._
def main(args: Array[String]): Unit = {
assert(isHello(extension_strip("hello")))
assert(!isHello(extension_strip("bonjour")))
assert(isHello(M.extension_strip("hello")))
assert(!isHello(M.extension_strip("bonjour")))
}
}
4 changes: 2 additions & 2 deletions tests/run/extension-methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ object Test extends App {

extension (x: Int) def em: Boolean = x > 0

assert(1.em == extension_em(1))
assert(1.em == this.extension_em(1))

case class Circle(x: Double, y: Double, radius: Double)

extension (c: Circle) def circumference: Double = c.radius * math.Pi * 2

val circle = new Circle(1, 1, 2.0)

assert(circle.circumference == extension_circumference(circle))
assert(circle.circumference == this.extension_circumference(circle))

extension (xs: Seq[String]) def longestStrings: Seq[String] = {
val maxLength = xs.map(_.length).max
Expand Down