@@ -952,8 +952,7 @@ object RefChecks {
952
952
* surprising names at runtime. E.g. in neg/i4564a.scala, a private
953
953
* case class `apply` method would have to be renamed to something else.
954
954
*/
955
- def checkNoPrivateOverrides (tree : Tree )(using Context ): Unit =
956
- val sym = tree.symbol
955
+ def checkNoPrivateOverrides (sym : Symbol )(using Context ): Unit =
957
956
if sym.maybeOwner.isClass
958
957
&& sym.is(Private )
959
958
&& (sym.isOneOf(MethodOrLazyOrMutable ) || ! sym.is(Local )) // in these cases we'll produce a getter later
@@ -1017,6 +1016,55 @@ object RefChecks {
1017
1016
1018
1017
end checkUnaryMethods
1019
1018
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
+
1020
1068
/** Verify that references in the user-defined `@implicitNotFound` message are valid.
1021
1069
* (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
1022
1070
*/
@@ -1150,8 +1198,8 @@ class RefChecks extends MiniPhase { thisPhase =>
1150
1198
1151
1199
override def transformValDef (tree : ValDef )(using Context ): ValDef = {
1152
1200
if tree.symbol.exists then
1153
- checkNoPrivateOverrides(tree)
1154
1201
val sym = tree.symbol
1202
+ checkNoPrivateOverrides(sym)
1155
1203
checkVolatile(sym)
1156
1204
if (sym.exists && sym.owner.isTerm) {
1157
1205
tree.rhs match {
@@ -1163,9 +1211,11 @@ class RefChecks extends MiniPhase { thisPhase =>
1163
1211
}
1164
1212
1165
1213
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)
1169
1219
tree
1170
1220
}
1171
1221
0 commit comments