Skip to content

Commit 1725258

Browse files
committed
Dealias before checking for member in lint
1 parent 8601228 commit 1725258

File tree

6 files changed

+94
-30
lines changed

6 files changed

+94
-30
lines changed

Diff for: compiler/src/dotty/tools/dotc/typer/RefChecks.scala

+27-28
Original file line numberDiff line numberDiff line change
@@ -1137,14 +1137,24 @@ object RefChecks {
11371137
end checkUnaryMethods
11381138

11391139
/** Check that an extension method is not hidden, i.e., that it is callable as an extension method.
1140+
*
1141+
* For example, it is not possible to define a type-safe extension `contains` for `Set`,
1142+
* since for any parameter type, the existing `contains` method will compile and would be used.
11401143
*
11411144
* An extension method is hidden if it does not offer a parameter that is not subsumed
11421145
* by the corresponding parameter of the member with the same name (or of all alternatives of an overload).
11431146
*
1144-
* This check is suppressed if this method is an override.
1147+
* This check is suppressed if the method is an override. (Because the type of the receiver
1148+
* may be narrower in the override.)
11451149
*
1146-
* For example, it is not possible to define a type-safe extension `contains` for `Set`,
1147-
* since for any parameter type, the existing `contains` method will compile and would be used.
1150+
* If the extension method is nullary, it is always hidden by a member of the same name.
1151+
* (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1152+
*
1153+
* This check is in lieu of a more expensive use-site check that an application failed to use an extension.
1154+
* That check would account for accessibility and opacity. As a limitation, this check considers
1155+
* only public members for which corresponding method parameters are either both opaque types or both not.
1156+
* It is intended to warn if the receiver type from a third-party library has been augmented with a member
1157+
* that nullifies an existing extension.
11481158
*
11491159
* If the member has a leading implicit parameter list, then the extension method must also have
11501160
* a leading implicit parameter list. The reason is that if the implicit arguments are inferred,
@@ -1155,42 +1165,31 @@ object RefChecks {
11551165
* If the member does not have a leading implicit parameter list, then the argument cannot be explicitly
11561166
* supplied with `using`, as typechecking would fail. But the extension method may have leading implicit
11571167
* parameters, which are necessarily supplied implicitly in the application. The first non-implicit
1158-
* parameters of the extension method must be distinguishable from the member parameters, as described.
1159-
*
1160-
* If the extension method is nullary, it is always hidden by a member of the same name.
1161-
* (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1162-
*
1163-
* This check is in lieu of a more expensive use-site check that an application failed to use an extension.
1164-
* That check would account for accessibility and opacity. As a limitation, this check considers
1165-
* only public members, a target receiver that is not an alias, and corresponding method parameters
1166-
* that are either both opaque types or both not.
1168+
* parameters of the extension method must be distinguishable from the member parameters, as described above.
11671169
*/
11681170
def checkExtensionMethods(sym: Symbol)(using Context): Unit =
11691171
if sym.is(Extension) then
11701172
extension (tp: Type)
11711173
def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true)
11721174
def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false }
11731175
val explicitInfo = sym.info.explicit // consider explicit value params
1174-
val target = explicitInfo.firstParamTypes.head.typeSymbol.info // required for extension method, the putative receiver
1176+
val target0 = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver
1177+
val target = target0.dealiasKeepOpaques.typeSymbol.info
11751178
val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter
1179+
def memberMatchesMethod(member: Denotation) =
1180+
val memberIsImplicit = member.info.hasImplicitParams
1181+
val paramTps =
1182+
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1183+
else methTp.explicit.firstParamTypes
1184+
inline def paramsCorrespond =
1185+
val memberParamTps = member.info.stripPoly.firstParamTypes
1186+
memberParamTps.corresponds(paramTps): (m, x) =>
1187+
m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias && (x frozen_<:< m)
1188+
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || paramsCorrespond
11761189
def hidden =
11771190
target.nonPrivateMember(sym.name)
11781191
.filterWithPredicate: member =>
1179-
member.symbol.isPublic && {
1180-
val memberIsImplicit = member.info.hasImplicitParams
1181-
val paramTps =
1182-
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1183-
else methTp.explicit.firstParamTypes
1184-
1185-
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || {
1186-
val memberParamTps = member.info.stripPoly.firstParamTypes
1187-
!memberParamTps.isEmpty
1188-
&& memberParamTps.lengthCompare(paramTps) == 0
1189-
&& memberParamTps.lazyZip(paramTps).forall: (m, x) =>
1190-
m.typeSymbol.denot.isOpaqueAlias == x.typeSymbol.denot.isOpaqueAlias
1191-
&& (x frozen_<:< m)
1192-
}
1193-
}
1192+
member.symbol.isPublic && memberMatchesMethod(member)
11941193
.exists
11951194
if sym.is(HasDefaultParams) then
11961195
val getterDenot =

Diff for: tests/warn/ext-override.scala

+1-1
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)

Diff for: tests/warn/i16743.scala

+1-1
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:

Diff for: tests/warn/i22232.scala

+5
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

Diff for: tests/warn/i22705.scala

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

Diff for: tests/warn/i22706.scala

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

0 commit comments

Comments
 (0)