Skip to content

Commit 13c8d09

Browse files
som-snytttgodzik
authored andcommitted
Dealias before checking for member in lint (scala#22708)
Fixes scala#22705 Fixes scala#22706 Fixes scala#22727 Follow-up to scala#22502 by inserting a `dealias` when arriving at `target` type. Refactored the body of `hidden` to make it easier to read. Adjusted the doc for the same reason. As a reminder to self, the original reason for special handling of aliases was due to subclassing, but overrides are excluded. (One could restore that warning for edge cases.) The long doc explaining the handling of leading implicits is moved to the end (as an appendix). Despite best efforts, I was unable to make the doc longer than the code. [Cherry-picked d362492]
1 parent 88bab9f commit 13c8d09

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)