Skip to content

Commit f50e66a

Browse files
authored
Merge pull request #256 from scala/backport-lts-3.3-22492
Backport "Discourage default arg for extension receiver" to 3.3 LTS
2 parents 5d82da0 + 6ec5712 commit f50e66a

File tree

6 files changed

+63
-9
lines changed

6 files changed

+63
-9
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ object desugar {
338338
.withMods(Modifiers(
339339
meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline),
340340
meth.mods.privateWithin))
341+
.withSpan(vparam.rhs.span)
341342
val rest = defaultGetters(vparams :: paramss1, n + 1)
342343
if vparam.rhs.isEmpty then rest else defaultGetter :: rest
343344
case _ :: paramss1 => // skip empty parameter lists and type parameters

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
220220
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204
221221
case GivenSearchPriorityID // errorNumber: 205
222222
case EnumMayNotBeValueClassesID // errorNumber: 206
223+
case IllegalUnrollPlacementID // errorNumber: 207 - unused in LTS
224+
case ExtensionHasDefaultID // errorNumber: 208
223225

224226
def errorNumber = ordinal - 1
225227

compiler/src/dotty/tools/dotc/reporting/messages.scala

+11
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,17 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context)
24742474
|
24752475
|The extension may be invoked as though selected from an arbitrary type if conversions are in play."""
24762476

2477+
class ExtensionHasDefault(method: Symbol)(using Context)
2478+
extends Message(ExtensionHasDefaultID):
2479+
def kind = MessageKind.PotentialIssue
2480+
def msg(using Context) =
2481+
i"""Extension method ${hl(method.name.toString)} should not have a default argument for its receiver."""
2482+
def explain(using Context) =
2483+
i"""The receiver cannot be omitted when an extension method is invoked as a selection.
2484+
|A default argument for that parameter would never be used in that case.
2485+
|An extension method can be invoked as a regular method, but if that is the intended usage,
2486+
|it should not be defined as an extension."""
2487+
24772488
class TraitCompanionWithMutableStatic()(using Context)
24782489
extends SyntaxMsg(TraitCompanionWithMutableStaticID) {
24792490
def msg(using Context) = i"Companion of traits cannot define mutable @static fields"

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

+16-9
Original file line numberDiff line numberDiff line change
@@ -1062,22 +1062,21 @@ object RefChecks {
10621062
* that are either both opaque types or both not.
10631063
*/
10641064
def checkExtensionMethods(sym: Symbol)(using Context): Unit =
1065-
if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then
1065+
if sym.is(Extension) then
10661066
extension (tp: Type)
1067-
def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType
1068-
def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes
1067+
def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true)
10691068
def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false }
1070-
val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver
1071-
val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter
1069+
val explicitInfo = sym.info.explicit // consider explicit value params
1070+
val target = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver
1071+
val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter
10721072
def hidden =
10731073
target.nonPrivateMember(sym.name)
1074-
.filterWithPredicate:
1075-
member =>
1074+
.filterWithPredicate: member =>
10761075
member.symbol.isPublic && {
10771076
val memberIsImplicit = member.info.hasImplicitParams
10781077
val paramTps =
10791078
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1080-
else methTp.firstExplicitParamTypes
1079+
else methTp.explicit.firstParamTypes
10811080

10821081
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || {
10831082
val memberParamTps = member.info.stripPoly.firstParamTypes
@@ -1089,7 +1088,15 @@ object RefChecks {
10891088
}
10901089
}
10911090
.exists
1092-
if !target.typeSymbol.isOpaqueAlias && hidden
1091+
if sym.is(HasDefaultParams) then
1092+
val getterDenot =
1093+
val receiverName = explicitInfo.firstParamNames.head
1094+
val num = sym.info.paramNamess.flatten.indexWhere(_ == receiverName)
1095+
val getterName = DefaultGetterName(sym.name.toTermName, num = num)
1096+
sym.owner.info.member(getterName)
1097+
if getterDenot.exists
1098+
then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos)
1099+
if !target.typeSymbol.isOpaqueAlias && !sym.nextOverriddenSymbol.exists && hidden
10931100
then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos)
10941101
end checkExtensionMethods
10951102

tests/warn/i12460.check

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- [E208] Potential Issue Warning: tests/warn/i12460.scala:3:23 --------------------------------------------------------
2+
3 |extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn
3+
| ^
4+
| Extension method invert should not have a default argument for its receiver.
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| The receiver cannot be omitted when an extension method is invoked as a selection.
9+
| A default argument for that parameter would never be used in that case.
10+
| An extension method can be invoked as a regular method, but if that is the intended usage,
11+
| it should not be defined as an extension.
12+
---------------------------------------------------------------------------------------------------------------------
13+
-- [E208] Potential Issue Warning: tests/warn/i12460.scala:5:37 --------------------------------------------------------
14+
5 |extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn
15+
| ^
16+
| Extension method revert should not have a default argument for its receiver.
17+
|---------------------------------------------------------------------------------------------------------------------
18+
| Explanation (enabled by `-explain`)
19+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
20+
| The receiver cannot be omitted when an extension method is invoked as a selection.
21+
| A default argument for that parameter would never be used in that case.
22+
| An extension method can be invoked as a regular method, but if that is the intended usage,
23+
| it should not be defined as an extension.
24+
---------------------------------------------------------------------------------------------------------------------

tests/warn/i12460.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -explain -Ystop-after:refchecks
2+
3+
extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn
4+
5+
extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn
6+
7+
extension (s: String)
8+
def divert(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok
9+
def divertimento(using String)(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok

0 commit comments

Comments
 (0)