@@ -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 =
0 commit comments