Skip to content

Commit 542de07

Browse files
authored
Merge pull request #313 from scala/backport-lts-3.3-22708
Backport "Dealias before checking for member in lint" to 3.3 LTS
2 parents f145a9d + b713012 commit 542de07

File tree

7 files changed

+106
-30
lines changed

7 files changed

+106
-30
lines changed

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

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,14 +1033,24 @@ object RefChecks {
10331033
end checkUnaryMethods
10341034

10351035
/** Check that an extension method is not hidden, i.e., that it is callable as an extension method.
1036+
*
1037+
* For example, it is not possible to define a type-safe extension `contains` for `Set`,
1038+
* since for any parameter type, the existing `contains` method will compile and would be used.
10361039
*
10371040
* An extension method is hidden if it does not offer a parameter that is not subsumed
10381041
* by the corresponding parameter of the member with the same name (or of all alternatives of an overload).
10391042
*
1040-
* This check is suppressed if this method is an override.
1043+
* This check is suppressed if the method is an override. (Because the type of the receiver
1044+
* may be narrower in the override.)
10411045
*
1042-
* For example, it is not possible to define a type-safe extension `contains` for `Set`,
1043-
* since for any parameter type, the existing `contains` method will compile and would be used.
1046+
* If the extension method is nullary, it is always hidden by a member of the same name.
1047+
* (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1048+
*
1049+
* This check is in lieu of a more expensive use-site check that an application failed to use an extension.
1050+
* That check would account for accessibility and opacity. As a limitation, this check considers
1051+
* only public members for which corresponding method parameters are either both opaque types or both not.
1052+
* It is intended to warn if the receiver type from a third-party library has been augmented with a member
1053+
* that nullifies an existing extension.
10441054
*
10451055
* If the member has a leading implicit parameter list, then the extension method must also have
10461056
* a leading implicit parameter list. The reason is that if the implicit arguments are inferred,
@@ -1051,42 +1061,31 @@ object RefChecks {
10511061
* If the member does not have a leading implicit parameter list, then the argument cannot be explicitly
10521062
* supplied with `using`, as typechecking would fail. But the extension method may have leading implicit
10531063
* parameters, which are necessarily supplied implicitly in the application. The first non-implicit
1054-
* parameters of the extension method must be distinguishable from the member parameters, as described.
1055-
*
1056-
* If the extension method is nullary, it is always hidden by a member of the same name.
1057-
* (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1058-
*
1059-
* This check is in lieu of a more expensive use-site check that an application failed to use an extension.
1060-
* That check would account for accessibility and opacity. As a limitation, this check considers
1061-
* only public members, a target receiver that is not an alias, and corresponding method parameters
1062-
* that are either both opaque types or both not.
1064+
* parameters of the extension method must be distinguishable from the member parameters, as described above.
10631065
*/
10641066
def checkExtensionMethods(sym: Symbol)(using Context): Unit =
10651067
if sym.is(Extension) then
10661068
extension (tp: Type)
10671069
def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true)
10681070
def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false }
10691071
val explicitInfo = sym.info.explicit // consider explicit value params
1070-
val target = explicitInfo.firstParamTypes.head.typeSymbol.info // required for extension method, the putative receiver
1072+
val target0 = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver
1073+
val target = target0.dealiasKeepOpaques.typeSymbol.info
10711074
val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter
1075+
def memberMatchesMethod(member: Denotation) =
1076+
val memberIsImplicit = member.info.hasImplicitParams
1077+
val paramTps =
1078+
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1079+
else methTp.explicit.firstParamTypes
1080+
inline def paramsCorrespond =
1081+
val memberParamTps = member.info.stripPoly.firstParamTypes
1082+
memberParamTps.corresponds(paramTps): (m, x) =>
1083+
m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias && (x frozen_<:< m)
1084+
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || paramsCorrespond
10721085
def hidden =
10731086
target.nonPrivateMember(sym.name)
10741087
.filterWithPredicate: member =>
1075-
member.symbol.isPublic && {
1076-
val memberIsImplicit = member.info.hasImplicitParams
1077-
val paramTps =
1078-
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1079-
else methTp.explicit.firstParamTypes
1080-
1081-
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || {
1082-
val memberParamTps = member.info.stripPoly.firstParamTypes
1083-
!memberParamTps.isEmpty
1084-
&& memberParamTps.lengthCompare(paramTps) == 0
1085-
&& memberParamTps.lazyZip(paramTps).forall: (m, x) =>
1086-
m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias
1087-
&& (x frozen_<:< m)
1088-
}
1089-
}
1088+
member.symbol.isPublic && memberMatchesMethod(member)
10901089
.exists
10911090
if sym.is(HasDefaultParams) then
10921091
val getterDenot =

tests/warn/ext-override.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//> using options -Xfatal-warnings
1+
//> using options -Werror
22

33
trait Foo[T]:
44
extension (x: T)

tests/warn/i16743.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ trait DungeonDweller:
6666
trait SadDungeonDweller:
6767
def f[A](x: Dungeon.IArray[A]) = 27 // x.length // just to confirm, length is not a member
6868

69-
trait Quote:
69+
trait Quote: // see tests/warn/ext-override.scala
7070
type Tree <: AnyRef
7171
given TreeMethods: TreeMethods
7272
trait TreeMethods:

tests/warn/i22232.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ object Upperbound3:
2323
object NonUpperbound1:
2424
opaque type MyString[+T] = String
2525
extension (arr: MyString[Byte]) def length: Int = 0 // nowarn
26+
2627
object NonUpperbound2:
2728
opaque type MyString[+T] = String
2829
extension [T <: MyString[Byte]](arr: T) def length2: Int = 0 // nowarn
2930

3031
object NonUpperbound3:
3132
opaque type MyString[+T] = String
3233
extension [T](arr: T) def length: Int = 0 // nowarn
34+
35+
object NonUpperbound4:
36+
opaque type MyString = String
37+
extension (arr: MyString) def length: Int = 0 // nowarn

tests/warn/i22705.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//> using options -Werror
2+
3+
object Native {
4+
class Obj:
5+
def f: String = "F"
6+
}
7+
8+
object Types {
9+
10+
opaque type Node = Native.Obj
11+
12+
type S = Node
13+
14+
object S:
15+
def apply(): S = new Node
16+
17+
extension (s: S)
18+
def f: String = "S"
19+
}
20+
21+
import Types.*
22+
23+
object Main {
24+
def main(args: Array[String]): Unit = {
25+
val v: S = S()
26+
println(v.f)
27+
}
28+
}

tests/warn/i22706.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -Werror
2+
3+
object Native {
4+
class O {
5+
def f: String = "F"
6+
}
7+
class M extends O
8+
}
9+
10+
object Types {
11+
opaque type N = Native.O
12+
opaque type GS = Native.M
13+
14+
type S = N | GS
15+
16+
object S:
17+
def apply(): S = new N
18+
19+
extension (s: S)
20+
def f: String = "S"
21+
}
22+
23+
import Types.*
24+
25+
object Main {
26+
def main(args: Array[String]): Unit = {
27+
val v: S = S()
28+
println(v.f)
29+
}
30+
}

tests/warn/i22727.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//> using options -Werror
2+
3+
object Main {
4+
type IXY = (Int, Int)
5+
6+
extension (xy: IXY) {
7+
def map(f: Int => Int): (Int, Int) = (f(xy._1), f(xy._2))
8+
}
9+
10+
def main(args: Array[String]): Unit = {
11+
val a = (0, 1)
12+
println(a)
13+
}
14+
}

0 commit comments

Comments
 (0)