Skip to content

Commit 6303e64

Browse files
committed
Warn if extension receiver has matching member
1 parent 2746ee8 commit 6303e64

File tree

11 files changed

+62
-5
lines changed

11 files changed

+62
-5
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ object SymDenotations {
13461346
* inClass <-- find denot.symbol class C { <-- symbol is here
13471347
*
13481348
* site: Subtype of both inClass and C
1349+
* } <-- balance the brace
13491350
*/
13501351
final def matchingDecl(inClass: Symbol, site: Type, name: Name = this.name)(using Context): Symbol = {
13511352
var denot = inClass.info.nonPrivateDecl(name)

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
206206
case PureUnitExpressionID // errorNumber: 190
207207
case MatchTypeLegacyPatternID // errorNumber: 191
208208
case UnstableInlineAccessorID // errorNumber: 192
209+
case ExtensionNullifiedByMemberID // errorNumber: 193
209210

210211
def errorNumber = ordinal - 1
211212

compiler/src/dotty/tools/dotc/reporting/messages.scala

+9
Original file line numberDiff line numberDiff line change
@@ -2429,6 +2429,15 @@ class SynchronizedCallOnBoxedClass(stat: tpd.Tree)(using Context)
24292429
|you intended."""
24302430
}
24312431

2432+
class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context)
2433+
extends Message(ExtensionNullifiedByMemberID):
2434+
def kind = MessageKind.PotentialIssue
2435+
def msg(using Context) = i"Suspicious extension ${hl(method.name.toString)} is already a member of ${hl(target.name.toString)}"
2436+
def explain(using Context) =
2437+
i"""Extension method ${hl(method.name.toString)} will never be selected
2438+
|because ${hl(target.name.toString)} already has a member with the same name.
2439+
|It can be called as a regular method, but should probably be defined that way."""
2440+
24322441
class TraitCompanionWithMutableStatic()(using Context)
24332442
extends SyntaxMsg(TraitCompanionWithMutableStaticID) {
24342443
def msg(using Context) = i"Companion of traits cannot define mutable @static fields"

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -2596,7 +2596,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25962596
linkConstructorParams(sym, tparamSyms, rhsCtx)
25972597

25982598
if sym.isInlineMethod then rhsCtx.addMode(Mode.InlineableBody)
2599-
if sym.is(ExtensionMethod) then rhsCtx.addMode(Mode.InExtensionMethod)
2599+
if sym.is(ExtensionMethod) then
2600+
if ctx.phase.isTyper then
2601+
val ValDef(_, paramTpt, _) = termParamssIn(paramss1).head.head
2602+
if !paramTpt.symbol.denot.isAliasType && !paramTpt.symbol.denot.isOpaqueAlias then
2603+
val other = paramTpt.tpe.nonPrivateMember(name)
2604+
if other.exists && sym.denot.info.resultType.matches(other.info) then
2605+
report.warning(ExtensionNullifiedByMember(sym, paramTpt.symbol), ddef.srcPos)
2606+
rhsCtx.addMode(Mode.InExtensionMethod)
26002607
val rhs1 = PrepareInlineable.dropInlineIfError(sym,
26012608
if sym.isScala2Macro then typedScala2MacroBody(ddef.rhs)(using rhsCtx)
26022609
else typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx))

compiler/test/dotty/tools/dotc/printing/PrintingTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import java.io.File
2525
class PrintingTest {
2626

2727
def options(phase: String, flags: List[String]) =
28-
List(s"-Xprint:$phase", "-color:never", "-classpath", TestConfiguration.basicClasspath) ::: flags
28+
List(s"-Xprint:$phase", "-color:never", "-nowarn", "-classpath", TestConfiguration.basicClasspath) ::: flags
2929

3030
private def compileFile(path: JPath, phase: String): Boolean = {
3131
val baseFilePath = path.toString.stripSuffix(".scala")

compiler/test/dotty/tools/scripting/ScriptTestEnv.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,10 @@ object ScriptTestEnv {
217217

218218
def toUrl: String = Paths.get(absPath).toUri.toURL.toString
219219

220+
// Used to be an extension on String
220221
// Treat norm paths with a leading '/' as absolute (Windows java.io.File#isAbsolute treats them as relative)
221-
def isAbsolute = p.norm.startsWith("/") || (isWin && p.norm.secondChar == ":")
222+
//@annotation.nowarn // hidden by Path#isAbsolute
223+
//def isAbsolute = p.norm.startsWith("/") || (isWin && p.norm.secondChar == ":")
222224
}
223225

224226
extension(f: File) {

scaladoc-testcases/src/tests/implicitConversions.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class B {
4545
class C {
4646
def extensionInCompanion: String = ???
4747
}
48-
48+
@annotation.nowarn // extensionInCompanion
4949
object C {
5050
implicit def companionConversion(c: C): B = ???
5151

@@ -70,4 +70,4 @@ package nested {
7070
}
7171

7272
class Z
73-
}
73+
}

scaladoc-testcases/src/tests/inheritedMembers1.scala

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tests
22
package inheritedMembers1
33

44

5+
/*<-*/@annotation.nowarn/*->*/
56
class A
67
{
78
def A: String

tests/neg/i16743.check

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- [E193] Potential Issue Error: tests/neg/i16743.scala:7:21 -----------------------------------------------------------
2+
7 |extension (x: T) def t = 27 // error
3+
| ^^^^^^^^^^
4+
| Suspicious extension t is already a member of T
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| Extension method t will never be selected
9+
| because T already has a member with the same name.
10+
| It can be called as a regular method, but should probably be defined that way.
11+
---------------------------------------------------------------------------------------------------------------------

tests/neg/i16743.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
//> using options -Werror -explain
3+
4+
trait T:
5+
def t = 42
6+
7+
extension (x: T) def t = 27 // error
8+
9+
@main def test() =
10+
val x = new T {}
11+
println:
12+
x.t
13+
14+
trait Foo:
15+
type X
16+
extension (x: X) def t: Int
17+
18+
trait Bar extends Foo:
19+
type X = T
20+
extension (x: X) def t = x.t
21+
22+
opaque type IArray[+T] = Array[? <: T]
23+
object IArray:
24+
extension (arr: IArray[Byte]) def length: Int = arr.asInstanceOf[Array[Byte]].length

tests/neg/i9241.scala

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final class Baz private (val x: Int) extends AnyVal {
2222
}
2323

2424
extension (x: Int)
25+
@annotation.nowarn
2526
def unary_- : Int = ???
2627
def unary_+[T] : Int = ???
2728
def unary_!() : Int = ??? // error

0 commit comments

Comments
 (0)