Skip to content

Commit 6037394

Browse files
committed
Discourage default arg for extension receiver
1 parent 590691b commit 6037394

File tree

5 files changed

+50
-9
lines changed

5 files changed

+50
-9
lines changed

Diff for: compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
221221
case GivenSearchPriorityID // errorNumber: 205
222222
case EnumMayNotBeValueClassesID // errorNumber: 206
223223
case IllegalUnrollPlacementID // errorNumber: 207
224+
case ExtensionHasDefaultID // errorNumber: 208
224225

225226
def errorNumber = ordinal - 1
226227

Diff for: compiler/src/dotty/tools/dotc/reporting/messages.scala

+9
Original file line numberDiff line numberDiff line change
@@ -2514,6 +2514,15 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context)
25142514
|
25152515
|The extension may be invoked as though selected from an arbitrary type if conversions are in play."""
25162516

2517+
class ExtensionHasDefault(method: Symbol)(using Context)
2518+
extends Message(ExtensionHasDefaultID):
2519+
def kind = MessageKind.PotentialIssue
2520+
def msg(using Context) =
2521+
i"""Extension method ${hl(method.name.toString)} should not have a default argument for its receiver."""
2522+
def explain(using Context) =
2523+
i"""Although extensions are ordinary methods, they must be invoked as a selection.
2524+
|Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used."""
2525+
25172526
class TraitCompanionWithMutableStatic()(using Context)
25182527
extends SyntaxMsg(TraitCompanionWithMutableStaticID) {
25192528
def msg(using Context) = i"Companion of traits cannot define mutable @static fields"

Diff for: compiler/src/dotty/tools/dotc/typer/RefChecks.scala

+13-9
Original file line numberDiff line numberDiff line change
@@ -1163,22 +1163,20 @@ object RefChecks {
11631163
* that are either both opaque types or both not.
11641164
*/
11651165
def checkExtensionMethods(sym: Symbol)(using Context): Unit =
1166-
if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then
1166+
if sym.is(Extension) then
11671167
extension (tp: Type)
1168-
def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType
1169-
def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes
1168+
def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true)
11701169
def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false }
1171-
val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver
1172-
val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter
1170+
val target = sym.info.explicit.firstParamTypes.head // required for extension method, the putative receiver
1171+
val methTp = sym.info.explicit.resultType // skip leading implicits and the "receiver" parameter
11731172
def hidden =
11741173
target.nonPrivateMember(sym.name)
1175-
.filterWithPredicate:
1176-
member =>
1174+
.filterWithPredicate: member =>
11771175
member.symbol.isPublic && {
11781176
val memberIsImplicit = member.info.hasImplicitParams
11791177
val paramTps =
11801178
if memberIsImplicit then methTp.stripPoly.firstParamTypes
1181-
else methTp.firstExplicitParamTypes
1179+
else methTp.explicit.firstParamTypes
11821180

11831181
paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || {
11841182
val memberParamTps = member.info.stripPoly.firstParamTypes
@@ -1190,7 +1188,13 @@ object RefChecks {
11901188
}
11911189
}
11921190
.exists
1193-
if !target.typeSymbol.isOpaqueAlias && hidden
1191+
val receiverName = sym.info.explicit.firstParamNames.head
1192+
val num = sym.info.paramNamess.flatten.indexWhere(_ == receiverName)
1193+
val getterName = DefaultGetterName(sym.name.toTermName, num = num)
1194+
val getterDenot = sym.owner.info.member(getterName)
1195+
if getterDenot.exists
1196+
then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos)
1197+
if !target.typeSymbol.isOpaqueAlias && !sym.nextOverriddenSymbol.exists && hidden
11941198
then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos)
11951199
end checkExtensionMethods
11961200

Diff for: tests/warn/i12460.check

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 must not have a default argument for its receiver.
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| Although extensions are ordinary methods, they must be invoked as a selection.
9+
| Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used.
10+
---------------------------------------------------------------------------------------------------------------------
11+
-- [E208] Potential Issue Warning: tests/warn/i12460.scala:5:17 --------------------------------------------------------
12+
5 |extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn
13+
| ^
14+
| Extension method revert must not have a default argument for its receiver.
15+
|---------------------------------------------------------------------------------------------------------------------
16+
| Explanation (enabled by `-explain`)
17+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18+
| Although extensions are ordinary methods, they must be invoked as a selection.
19+
| Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used.
20+
---------------------------------------------------------------------------------------------------------------------

Diff for: tests/warn/i12460.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -explain
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) def divert(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok

0 commit comments

Comments
 (0)