8000 Discourage default arg for extension receiver by som-snytt · Pull Request #22492 · scala/scala3 · GitHub
[go: up one dir, main page]

Skip to content

Discourage default arg for extension receiver #22492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Discourage default arg for extension receiver
  • Loading branch information
som-snytt committed Feb 19, 2025
commit fd9ebe0ea563a02f42bd72792a2495e3aebdbabc
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case GivenSearchPriorityID // errorNumber: 205
case EnumMayNotBeValueClassesID // errorNumber: 206
case IllegalUnrollPlacementID // errorNumber: 207
case ExtensionHasDefaultID // errorNumber: 208

def errorNumber = ordinal - 1

Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,15 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context)
|
|The extension may be invoked as though selected from an arbitrary type if conversions are in play."""

class ExtensionHasDefault(method: Symbol)(using Context)
extends Message(ExtensionHasDefaultID):
def kind = MessageKind.PotentialIssue
def msg(using Context) =
i"""Extension method ${hl(method.name.toString)} should not have a default argument for its receiver."""
def explain(using Context) =
i"""Although extensions are ordinary methods, they must be invoked as a selection.
|Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I think that this message might be confusing, since it's technically false. They don't have to be invoked as a selection.
I can agree on adding a warning here, since it in most cases it's undesired to have a default value for a receiver of an extension method, but I we should make the message conditional -- e.g. When invoking an extension method as a selection, the receiver cannot be omitted.


class TraitCompanionWithMutableStatic()(using Context)
extends SyntaxMsg(TraitCompanionWithMutableStaticID) {
def msg(using Context) = i"Companion of traits cannot define mutable @static fields"
Expand Down
22 changes: 13 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1163,22 +1163,20 @@ object RefChecks {
* that are either both opaque types or both not.
*/
def checkExtensionMethods(sym: Symbol)(using Context): Unit =
if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then
if sym.is(Extension) then
extension (tp: Type)
def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType
def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes
def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true)
def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false }
val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver
val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter
val target = sym.info.explicit.firstParamTypes.head // required for extension method, the putative receiver
val methTp = sym.info.explicit.resultType // skip leading implicits and the "receiver" parameter
def hidden =
target.nonPrivateMember(sym.name)
.filterWithPredicate:
member =>
.filterWithPredicate: member =>
member.symbol.isPublic && {
val memberIsImplicit = member.info.hasImplicitParams
val paramTps =
if memberIsImplicit then methTp.stripPoly.firstParamTypes
else methTp.firstExplicitParamTypes
else methTp.explicit.firstParamTypes

paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || {
val memberParamTps = member.info.stripPoly.firstParamTypes
Expand All @@ -1190,7 +1188,13 @@ object RefChecks {
}
}
.exists
if !target.typeSymbol.isOpaqueAlias && hidden
val receiverName = sym.info.explicit.firstParamNames.head
val num = sym.info.paramNamess.flatten.indexWhere(_ == receiverName)
val getterName = DefaultGetterName(sym.name.toTermName, num = num)
val getterDenot = sym.owner.info.member(getterName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you move that into a local function? The logic might be slightly clearer then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also leverage HasDefaultParams

if getterDenot.exists
then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos)
if !target.typeSymbol.isOpaqueAlias && !sym.nextOverriddenSymbol.exists && hidden
then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos)
end checkExtensionMethods

Expand Down
20 changes: 20 additions & 0 deletions tests/warn/i12460.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- [E208] Potential Issue Warning: tests/warn/i12460.scala:3:23 --------------------------------------------------------
3 |extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn
| ^
| Extension method invert should not have a default argument for its receiver.
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Although extensions are ordinary methods, they must be invoked as a selection.
| Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used.
---------------------------------------------------------------------------------------------------------------------
-- [E208] Potential Issue Warning: tests/warn/i12460.scala:5:17 --------------------------------------------------------
5 |extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn
| ^
| Extension method revert should not have a default argument for its receiver.
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Although extensions are ordinary methods, they must be invoked as a selection.
| Therefore, the receiver cannot be omitted. A default argument for that parameter would never be used.
---------------------------------------------------------------------------------------------------------------------
7 changes: 7 additions & 0 deletions tests/warn/i12460.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//> using options -explain

extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn

extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn

extension (s: String) def divert(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok
0