@@ -1033,8 +1033,7 @@ object RefChecks {
10331033 * surprising names at runtime. E.g. in neg/i4564a.scala, a private
10341034 * case class `apply` method would have to be renamed to something else.
10351035 */
1036- def checkNoPrivateOverrides (tree : Tree )(using Context ): Unit =
1037- val sym = tree.symbol
1036+ def checkNoPrivateOverrides (sym : Symbol )(using Context ): Unit =
10381037 if sym.maybeOwner.isClass
10391038 && sym.is(Private )
10401039 && (sym.isOneOf(MethodOrLazyOrMutable ) || ! sym.is(Local )) // in these cases we'll produce a getter later
@@ -1100,6 +1099,55 @@ object RefChecks {
11001099
11011100 end checkUnaryMethods
11021101
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+
11031151 /** Verify that references in the user-defined `@implicitNotFound` message are valid.
11041152 * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
11051153 */
@@ -1233,8 +1281,8 @@ class RefChecks extends MiniPhase { thisPhase =>
12331281
12341282 override def transformValDef (tree : ValDef )(using Context ): ValDef = {
12351283 if tree.symbol.exists then
1236- checkNoPrivateOverrides(tree)
12371284 val sym = tree.symbol
1285+ checkNoPrivateOverrides(sym)
12381286 checkVolatile(sym)
12391287 if (sym.exists && sym.owner.isTerm) {
12401288 tree.rhs match {
@@ -1246,9 +1294,11 @@ class RefChecks extends MiniPhase { thisPhase =>
12461294 }
12471295
12481296 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)
12521302 tree
12531303 }
12541304
0 commit comments