@@ -952,8 +952,7 @@ object RefChecks {
952952 * surprising names at runtime. E.g. in neg/i4564a.scala, a private
953953 * case class `apply` method would have to be renamed to something else.
954954 */
955- def checkNoPrivateOverrides (tree : Tree )(using Context ): Unit =
956- val sym = tree.symbol
955+ def checkNoPrivateOverrides (sym : Symbol )(using Context ): Unit =
957956 if sym.maybeOwner.isClass
958957 && sym.is(Private )
959958 && (sym.isOneOf(MethodOrLazyOrMutable ) || ! sym.is(Local )) // in these cases we'll produce a getter later
@@ -1017,6 +1016,55 @@ object RefChecks {
10171016
10181017 end checkUnaryMethods
10191018
1019+ /** Check that an extension method is not hidden, i.e., that it is callable as an extension method.
1020+ *
1021+ * An extension method is hidden if it does not offer a parameter that is not subsumed
1022+ * by the corresponding parameter of the member with the same name (or of all alternatives of an overload).
1023+ *
1024+ * For example, it is not possible to define a type-safe extension `contains` for `Set`,
1025+ * since for any parameter type, the existing `contains` method will compile and would be used.
1026+ *
1027+ * If the member has a leading implicit parameter list, then the extension method must also have
1028+ * a leading implicit parameter list. The reason is that if the implicit arguments are inferred,
1029+ * either the member method is used or typechecking fails. If the implicit arguments are supplied
1030+ * explicitly and the member method is not applicable, the extension is checked, and its parameters
1031+ * must be implicit in order to be applicable.
1032+ *
1033+ * If the member does not have a leading implicit parameter list, then the argument cannot be explicitly
1034+ * supplied with `using`, as typechecking would fail. But the extension method may have leading implicit
1035+ * parameters, which are necessarily supplied implicitly in the application. The first non-implicit
1036+ * parameters of the extension method must be distinguishable from the member parameters, as described.
1037+ *
1038+ * If the extension method is nullary, it is always hidden by a member of the same name.
1039+ * (Either the member is nullary, or the reference is taken as the eta-expansion of the member.)
1040+ */
1041+ def checkExtensionMethods (sym : Symbol )(using Context ): Unit = if sym.is(Extension ) then
1042+ extension (tp : Type )
1043+ def strippedResultType = Applications .stripImplicit(tp.stripPoly, wildcardOnly = true ).resultType
1044+ def firstExplicitParamTypes = Applications .stripImplicit(tp.stripPoly, wildcardOnly = true ).firstParamTypes
1045+ def hasImplicitParams = tp.stripPoly match { case mt : MethodType => mt.isImplicitMethod case _ => false }
1046+ val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver
1047+ val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter
1048+ def hidden =
1049+ target.nonPrivateMember(sym.name)
1050+ .filterWithPredicate:
1051+ member =>
1052+ val memberIsImplicit = member.info.hasImplicitParams
1053+ val paramTps =
1054+ if memberIsImplicit then methTp.stripPoly.firstParamTypes
1055+ else methTp.firstExplicitParamTypes
1056+
1057+ paramTps.isEmpty || memberIsImplicit && ! methTp.hasImplicitParams || {
1058+ val memberParamTps = member.info.stripPoly.firstParamTypes
1059+ ! memberParamTps.isEmpty
1060+ && memberParamTps.lengthCompare(paramTps) == 0
1061+ && memberParamTps.lazyZip(paramTps).forall((m, x) => x frozen_<:< m)
1062+ }
1063+ .exists
1064+ if ! target.typeSymbol.denot.isAliasType && ! target.typeSymbol.denot.isOpaqueAlias && hidden
1065+ then report.warning(ExtensionNullifiedByMember (sym, target.typeSymbol), sym.srcPos)
1066+ end checkExtensionMethods
1067+
10201068 /** Verify that references in the user-defined `@implicitNotFound` message are valid.
10211069 * (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
10221070 */
@@ -1150,8 +1198,8 @@ class RefChecks extends MiniPhase { thisPhase =>
11501198
11511199 override def transformValDef (tree : ValDef )(using Context ): ValDef = {
11521200 if tree.symbol.exists then
1153- checkNoPrivateOverrides(tree)
11541201 val sym = tree.symbol
1202+ checkNoPrivateOverrides(sym)
11551203 checkVolatile(sym)
11561204 if (sym.exists && sym.owner.isTerm) {
11571205 tree.rhs match {
@@ -1163,9 +1211,11 @@ class RefChecks extends MiniPhase { thisPhase =>
11631211 }
11641212
11651213 override def transformDefDef (tree : DefDef )(using Context ): DefDef = {
1166- checkNoPrivateOverrides(tree)
1167- checkImplicitNotFoundAnnotation.defDef(tree.symbol.denot)
1168- checkUnaryMethods(tree.symbol)
1214+ val sym = tree.symbol
1215+ checkNoPrivateOverrides(sym)
1216+ checkImplicitNotFoundAnnotation.defDef(sym.denot)
1217+ checkUnaryMethods(sym)
1218+ checkExtensionMethods(sym)
11691219 tree
11701220 }
11711221
0 commit comments