Skip to content

Warn if implicit default shadows given #23559

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ private sealed trait WarningSettings:
private val WimplausiblePatterns = BooleanSetting(WarningSetting, "Wimplausible-patterns", "Warn if comparison with a pattern value looks like it might always fail.")
private val WunstableInlineAccessors = BooleanSetting(WarningSetting, "WunstableInlineAccessors", "Warn an inline methods has references to non-stable binary APIs.")
private val WtoStringInterpolated = BooleanSetting(WarningSetting, "Wtostring-interpolated", "Warn a standard interpolator used toString on a reference type.")
private val WrecurseWithDefault = BooleanSetting(WarningSetting, "Wrecurse-with-default", "Warn when a method calls itself with a default argument.")
private val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(
WarningSetting,
name = "Wunused",
Expand Down Expand Up @@ -309,6 +310,7 @@ private sealed trait WarningSettings:
def implausiblePatterns(using Context): Boolean = allOr(WimplausiblePatterns)
def unstableInlineAccessors(using Context): Boolean = allOr(WunstableInlineAccessors)
def toStringInterpolated(using Context): Boolean = allOr(WtoStringInterpolated)
def recurseWithDefault(using Context): Boolean = allOr(WrecurseWithDefault)
def checkInit(using Context): Boolean = allOr(WcheckInit)

/** -X "Extended" or "Advanced" settings */
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case UnnecessaryNN // errorNumber: 216
case ErasedNotPureID // errorNumber: 217
case IllegalErasedDefID // errorNumber: 218
case DefaultShadowsGivenID // errorNumber: 219
case RecurseWithDefaultID // errorNumber: 220

def errorNumber = ordinal - 1

Expand Down
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3652,3 +3652,15 @@ final class IllegalErasedDef(sym: Symbol)(using Context) extends TypeMsg(Illegal
override protected def explain(using Context): String =
"Only non-lazy immutable values can be `erased`"
end IllegalErasedDef

final class DefaultShadowsGiven(name: Name)(using Context) extends TypeMsg(DefaultShadowsGivenID):
override protected def msg(using Context): String =
i"Argument for implicit parameter $name was supplied using a default argument."
override protected def explain(using Context): String =
"Usually the given in scope is intended, but you must specify it after explicit `using`."

final class RecurseWithDefault(name: Name)(using Context) extends TypeMsg(RecurseWithDefaultID):
override protected def msg(using Context): String =
i"Recursive call used a default argument for parameter $name."
override protected def explain(using Context): String =
"It's more explicit to pass current or modified arguments in a recursion."
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/TailRec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ package transform
import ast.{TreeTypeMap, tpd}
import config.Printers.tailrec
import core.*
import Contexts.*, Flags.*, Symbols.*, Decorators.em
import Contexts.*, Flags.*, Symbols.*, Decorators.*
import Constants.Constant
import NameKinds.{TailLabelName, TailLocalName, TailTempName}
import NameKinds.{DefaultGetterName, TailLabelName, TailLocalName, TailTempName}
import StdNames.nme
import reporting.*
import transform.MegaPhase.MiniPhase
Expand Down Expand Up @@ -325,7 +325,14 @@ class TailRec extends MiniPhase {
method.matches(calledMethod) &&
enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias

if (isRecursiveCall)
if isRecursiveCall then
if ctx.settings.Whas.recurseWithDefault then
tree.args.find(_.symbol.name.is(DefaultGetterName)) match
case Some(arg) =>
val DefaultGetterName(_, index) = arg.symbol.name: @unchecked
report.warning(RecurseWithDefault(calledMethod.info.firstParamNames(index)), tree.srcPos)
case _ =>

if (inTailPosition) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be a tailrec call only if inTailPosition is true.

tailrec.println("Rewriting tail recursive call: " + tree.span)
rewrote = true
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,11 @@ trait Applications extends Compatibility {
methodType.isImplicitMethod && (applyKind == ApplyKind.Using || failEmptyArgs)

if !defaultArg.isEmpty then
if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled)
&& !inferImplicitArg(formal, appPos.span).tpe.isError
then
report.warning(DefaultShadowsGiven(methodType.paramNames(n)), appPos)

defaultArg.tpe.widen match
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)
Expand Down
53 changes: 34 additions & 19 deletions scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package dotty.tools.scaladoc
package tasty

import scala.jdk.CollectionConverters._

import scala.quoted._
import scala.annotation.*
import scala.jdk.CollectionConverters.*
import scala.quoted.*
import scala.util.control.NonFatal

import NameNormalizer._
import SyntheticsSupport._
import NameNormalizer.*
import SyntheticsSupport.*

trait TypesSupport:
self: TastyParser =>
Expand Down Expand Up @@ -155,24 +155,25 @@ trait TypesSupport:
.reduceLeftOption((acc: SSignature, elem: SSignature) => acc ++ plain(", ").l ++ elem).getOrElse(List())
++ plain(")").l

def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature = ( info match {
def parseRefinedElem(name: String, info: TypeRepr, polyTyped: SSignature = Nil): SSignature =
val ssig = info match
case m: MethodType => {
val paramList = getParamList(m)
keyword("def ").l ++ plain(name).l ++ polyTyped ++ paramList ++ plain(": ").l ++ inner(m.resType, skipThisTypePrefix)
}
case t: PolyType => {
case t: PolyType =>
val paramBounds = getParamBounds(t)
val parsedMethod = parseRefinedElem(name, t.resType)
if (!paramBounds.isEmpty){
if !paramBounds.isEmpty then
parseRefinedElem(name, t.resType, plain("[").l ++ paramBounds ++ plain("]").l)
} else parseRefinedElem(name, t.resType)
}
else
parseRefinedElem(name, t.resType, polyTyped = Nil)
case ByNameType(tp) => keyword("def ").l ++ plain(s"$name: ").l ++ inner(tp, skipThisTypePrefix)
case t: TypeBounds => keyword("type ").l ++ plain(name).l ++ inner(t, skipThisTypePrefix)
case t: TypeRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix)
case t: TermRef => keyword("val ").l ++ plain(s"$name: ").l ++ inner(t, skipThisTypePrefix)
case other => noSupported(s"Not supported type in refinement $info")
} ) ++ plain("; ").l

ssig ++ plain("; ").l

def parsePolyFunction(info: TypeRepr): SSignature = info match {
case t: PolyType =>
Expand Down Expand Up @@ -253,6 +254,7 @@ trait TypesSupport:
}) ++ plain("]").l

case tp @ TypeRef(qual, typeName) =>
inline def wrapping = shouldWrapInParens(inner = qual, outer = tp, isLeft = true)
qual match {
case r: RecursiveThis => tpe(s"this.$typeName").l
case ThisType(tr) =>
Expand All @@ -269,23 +271,28 @@ trait TypesSupport:
if skipPrefix(qual, elideThis, originalOwner, skipThisTypePrefix) then
tpe(tp.typeSymbol)
else
val sig = inParens(inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true), shouldWrapInParens(qual, tp, true))
sig ++ plain(".").l ++ tpe(tp.typeSymbol)
val sig = inParens(
inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true), wrapping)
sig
++ plain(".").l
++ tpe(tp.typeSymbol)

case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) =>
tpe(tp.typeSymbol)
case _: TermRef | _: ParamRef =>
val suffix = if tp.typeSymbol == Symbol.noSymbol then tpe(typeName).l else tpe(tp.typeSymbol)
inner(qual, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l ++ suffix
inner(qual, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true)
++ plain(".").l
++ suffix
case _ =>
val sig = inParens(inner(qual, skipThisTypePrefix), shouldWrapInParens(qual, tp, true))
val sig = inParens(inner(qual, skipThisTypePrefix), wrapping)
sig ++ keyword("#").l ++ tpe(tp.typeSymbol)
}

case tr @ TermRef(qual, typeName) =>
val prefix = qual match
case t if skipPrefix(t, elideThis, originalOwner, skipThisTypePrefix) => Nil
case tp => inner(tp, skipThisTypePrefix)(using skipTypeSuffix = true) ++ plain(".").l
case tp => inner(tp, skipThisTypePrefix)(using indent = indent, skipTypeSuffix = true) ++ plain(".").l
val suffix = if skipTypeSuffix then Nil else List(plain("."), keyword("type"))
val typeSig = tr.termSymbol.tree match
case vd: ValDef if tr.termSymbol.flags.is(Flags.Module) =>
Expand All @@ -304,9 +311,17 @@ trait TypesSupport:
val spaces = " " * (indent)
val casesTexts = cases.flatMap {
case MatchCase(from, to) =>
keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l
keyword(caseSpaces + "case ").l
++ inner(from, skipThisTypePrefix)
++ keyword(" => ").l
++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix)
++ plain("\n").l
case TypeLambda(_, _, MatchCase(from, to)) =>
keyword(caseSpaces + "case ").l ++ inner(from, skipThisTypePrefix) ++ keyword(" => ").l ++ inner(to, skipThisTypePrefix)(using indent = indent + 2) ++ plain("\n").l
keyword(caseSpaces + "case ").l
++ inner(from, skipThisTypePrefix)
++ keyword(" => ").l
++ inner(to, skipThisTypePrefix)(using indent = indent + 2, skipTypeSuffix = skipTypeSuffix)
++ plain("\n").l
}
inner(sc, skipThisTypePrefix) ++ keyword(" match ").l ++ plain("{\n").l ++ casesTexts ++ plain(spaces + "}").l

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------
-- [E172] Type Error: tests/neg/i19414-desugared.scala:22:34 -----------------------------------------------------------
22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
| ^
|No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef.
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/neg/19414.check → tests/neg/i19414.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- [E172] Type Error: tests/neg/19414.scala:15:34 ----------------------------------------------------------------------
-- [E172] Type Error: tests/neg/i19414.scala:15:34 ---------------------------------------------------------------------
15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
| ^
|No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef.
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions tests/warn/i23541.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- [E219] Type Warning: tests/warn/i23541.scala:29:13 ------------------------------------------------------------------
29 | println(f(using s = "ab")) // warn uses default instead of given // prints "ab"
| ^^^^^^^^^^^^^^^^^
| Argument for implicit parameter i was supplied using a default argument.
|
| longer explanation available when compiling with `-explain`
-- [E220] Type Warning: tests/warn/i23541.scala:5:17 -------------------------------------------------------------------
5 | else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Recursive call used a default argument for parameter q.
|
| longer explanation available when compiling with `-explain`
-- [E220] Type Warning: tests/warn/i23541.scala:9:17 -------------------------------------------------------------------
9 | else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack)
| ^^^^^^^^^^^^^^^^^^^^^
| Recursive call used a default argument for parameter q.
|
| longer explanation available when compiling with `-explain`
30 changes: 30 additions & 0 deletions tests/warn/i23541.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//> using options -Wrecurse-with-default

def fun(x: Int)(using p: Int, q: Int = 0): Int =
if x <= 0 then p * q
else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack)

def gun(x: Int)(p: Int, q: Int = 0): Int =
if x <= 0 then p * q
else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack)

def nested(using x: Int, y: Int = 42): Int =
def f: Int = nested(using x) // nowarn only self-recursive tailrec is eligible for warning
f

def f(using s: String, i: Int = 1): String = s * i
def g(using s: String)(using i: Int = 1): String = s * i

@main def Test =
println(fun(3)(using p = 0, q = 1))
locally:
given String = "ab"
println(f) // prints "ab"
println(g) // prints "ab"
locally:
println(f(using s = "ab")) // prints "ab"
println(g(using s = "ab")) // prints "ab"
locally:
given Int = 2
println(f(using s = "ab")) // warn uses default instead of given // prints "ab"
println(g(using s = "ab")) // prints "abab"
Loading