@@ -1033,8 +1033,7 @@ object RefChecks {
1033
1033
* surprising names at runtime. E.g. in neg/i4564a.scala, a private
1034
1034
* case class `apply` method would have to be renamed to something else.
1035
1035
*/
1036
- def checkNoPrivateOverrides (tree : Tree )(using Context ): Unit =
1037
- val sym = tree.symbol
1036
+ def checkNoPrivateOverrides (sym : Symbol )(using Context ): Unit =
1038
1037
if sym.maybeOwner.isClass
1039
1038
&& sym.is(Private )
1040
1039
&& (sym.isOneOf(MethodOrLazyOrMutable ) || ! sym.is(Local )) // in these cases we'll produce a getter later
@@ -1100,6 +1099,55 @@ object RefChecks {
1100
1099
1101
1100
end checkUnaryMethods
1102
1101
1102
+ /** Check that an extension method is not hidden, i.e., that it is callable as an extension method.
1103
+ *
1104
+ * An extension method is hidden if it does not offer a parameter that is not subsumed
1105
+ * by the corresponding parameter of the member with the same name (or of all alternatives of an overload).
1106
+ *
1107
+ * For example, it is not possible to define a type-safe extension `contains` for `Set`,
1108
+ * since for any parameter type, the existing `contains` method will compile and would be used.
1109
+ *
1110
+ * If the member has a leading implicit parameter list, then the extension method must also have
1111
+ * a leading implicit parameter list. The reason is that if the implicit arguments are inferred,
1112
+ * either the member method is used or typechecking fails. If the implicit arguments are supplied
1113
+ * explicitly and the member method is not applicable, the extension is checked, and its parameters
1114
+ * must be implicit in order to be applicable.
1115
+ *
1116
+ * If the member does not have a leading implicit parameter list, then the argument cannot be explicitly
1117
+ * supplied with `using`, as typechecking would fail. But the extension method may have leading implicit
1118
+ * parameters, which are necessarily supplied implicitly in the application. The first non-implicit
1119
+ * parameters of the extension method must be distinguishable from the member parameters, as described.
1120
+ *
1121
+ * If the extension method is nullary, it is always hidden by a member of the same name.
1122
+ * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1123
+ */
1124
+ def checkExtensionMethods (sym : Symbol )(using Context ): Unit = if sym.is(Extension ) then
1125
+ extension (tp : Type )
1126
+ def strippedResultType = Applications .stripImplicit(tp.stripPoly, wildcardOnly = true ).resultType
1127
+ def firstExplicitParamTypes = Applications .stripImplicit(tp.stripPoly, wildcardOnly = true ).firstParamTypes
1128
+ def hasImplicitParams = tp.stripPoly match { case mt : MethodType => mt.isImplicitMethod case _ => false }
1129
+ val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver
1130
+ val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter
1131
+ def hidden =
1132
+ target.nonPrivateMember(sym.name)
1133
+ .filterWithPredicate:
1134
+ member =>
1135
+ val memberIsImplicit = member.info.hasImplicitParams
1136
+ val paramTps =
1137
+ if memberIsImplicit then methTp.stripPoly.firstParamTypes
1138
+ else methTp.firstExplicitParamTypes
1139
+
1140
+ paramTps.isEmpty || memberIsImplicit && ! methTp.hasImplicitParams || {
1141
+ val memberParamTps = member.info.stripPoly.firstParamTypes
1142
+ ! memberParamTps.isEmpty
1143
+ && memberParamTps.lengthCompare(paramTps) == 0
1144
+ && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m)
1145
+ }
1146
+ .exists
1147
+ if ! target.typeSymbol.denot.isAliasType && ! target.typeSymbol.denot.isOpaqueAlias && hidden
1148
+ then report.warning(ExtensionNullifiedByMember (sym, target.typeSymbol), sym.srcPos)
1149
+ end checkExtensionMethods
1150
+
1103
1151
/** Verify that references in the user-defined `@implicitNotFound` message are valid.
1104
1152
* (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
1105
1153
*/
@@ -1233,8 +1281,8 @@ class RefChecks extends MiniPhase { thisPhase =>
1233
1281
1234
1282
override def transformValDef (tree : ValDef )(using Context ): ValDef = {
1235
1283
if tree.symbol.exists then
1236
- checkNoPrivateOverrides(tree)
1237
1284
val sym = tree.symbol
1285
+ checkNoPrivateOverrides(sym)
1238
1286
checkVolatile(sym)
1239
1287
if (sym.exists && sym.owner.isTerm) {
1240
1288
tree.rhs match {
@@ -1246,9 +1294,11 @@ class RefChecks extends MiniPhase { thisPhase =>
1246
1294
}
1247
1295
1248
1296
override def transformDefDef (tree : DefDef )(using Context ): DefDef = {
1249
- checkNoPrivateOverrides(tree)
1250
- checkImplicitNotFoundAnnotation.defDef(tree.symbol.denot)
1251
- checkUnaryMethods(tree.symbol)
1297
+ val sym = tree.symbol
1298
+ checkNoPrivateOverrides(sym)
1299
+ checkImplicitNotFoundAnnotation.defDef(sym.denot)
1300
+ checkUnaryMethods(sym)
1301
+ checkExtensionMethods(sym)
1252
1302
tree
1253
1303
}
1254
1304
0 commit comments