Skip to content

Commit f038825

Browse files
committed
Warn if implicit default shadows given
1 parent 73b935c commit f038825

File tree

10 files changed

+65
-4
lines changed

10 files changed

+65
-4
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ private sealed trait WarningSettings:
167167
private val WimplausiblePatterns = BooleanSetting(WarningSetting, "Wimplausible-patterns", "Warn if comparison with a pattern value looks like it might always fail.")
168168
private val WunstableInlineAccessors = BooleanSetting(WarningSetting, "WunstableInlineAccessors", "Warn an inline methods has references to non-stable binary APIs.")
169169
private val WtoStringInterpolated = BooleanSetting(WarningSetting, "Wtostring-interpolated", "Warn a standard interpolator used toString on a reference type.")
170+
private val WrecurseWithDefault = BooleanSetting(WarningSetting, "Wtailrec-default", "Warn when a tail-recursive call uses a default arg.")
170171
private val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(
171172
WarningSetting,
172173
name = "Wunused",
@@ -309,6 +310,7 @@ private sealed trait WarningSettings:
309310
def implausiblePatterns(using Context): Boolean = allOr(WimplausiblePatterns)
310311
def unstableInlineAccessors(using Context): Boolean = allOr(WunstableInlineAccessors)
311312
def toStringInterpolated(using Context): Boolean = allOr(WtoStringInterpolated)
313+
def recurseWithDefault(using Context): Boolean = allOr(WrecurseWithDefault)
312314
def checkInit(using Context): Boolean = allOr(WcheckInit)
313315

314316
/** -X "Extended" or "Advanced" settings */

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
232232
case UnnecessaryNN // errorNumber: 216
233233
case ErasedNotPureID // errorNumber: 217
234234
case IllegalErasedDefID // errorNumber: 218
235+
case DefaultShadowsGivenID // errorNumber: 219
236+
case TailrecUsedDefaultID // errorNumber: 220
235237

236238
def errorNumber = ordinal - 1
237239

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3652,3 +3652,15 @@ final class IllegalErasedDef(sym: Symbol)(using Context) extends TypeMsg(Illegal
36523652
override protected def explain(using Context): String =
36533653
"Only non-lazy immutable values can be `erased`"
36543654
end IllegalErasedDef
3655+
3656+
final class DefaultShadowsGiven(name: Name)(using Context) extends TypeMsg(DefaultShadowsGivenID):
3657+
override protected def msg(using Context): String =
3658+
i"Argument for implicit parameter $name was supplied using a default argument."
3659+
override protected def explain(using Context): String =
3660+
"Usually it's intended to prefer the given in scope, but you must specify it explicitly."
3661+
3662+
final class TailrecUsedDefault(using Context) extends TypeMsg(TailrecUsedDefaultID):
3663+
override protected def msg(using Context): String =
3664+
i"Recursive call used a default argument."
3665+
override protected def explain(using Context): String =
3666+
"Usually it's intended to pass current arguments in a recursion."

compiler/src/dotty/tools/dotc/transform/TailRec.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import NameKinds.{TailLabelName, TailLocalName, TailTempName}
1010
import StdNames.nme
1111
import reporting.*
1212
import transform.MegaPhase.MiniPhase
13+
import typer.Applications.UsedDefaults
1314
import util.LinearSet
1415
import dotty.tools.uncheckedNN
1516

@@ -325,7 +326,10 @@ class TailRec extends MiniPhase {
325326
method.matches(calledMethod) &&
326327
enclosingClass.appliedRef.widen <:< prefix.tpe.widenDealias
327328

328-
if (isRecursiveCall)
329+
if isRecursiveCall then
330+
if ctx.settings.Whas.recurseWithDefault && tree.args.exists(_.hasAttachment(UsedDefaults)) then
331+
report.warning(TailrecUsedDefault(), tree.srcPos)
332+
329333
if (inTailPosition) {
330334
tailrec.println("Rewriting tail recursive call: " + tree.span)
331335
rewrote = true

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package typer
55
import core.*
66
import ast.{Trees, tpd, untpd, desugar}
77
import util.Stats.record
8-
import util.{SrcPos, NoSourcePosition}
8+
import util.{Property, SrcPos, NoSourcePosition}
99
import Contexts.*
1010
import Flags.*
1111
import Symbols.*
@@ -42,6 +42,9 @@ import dotty.tools.dotc.inlines.Inlines
4242
object Applications {
4343
import tpd.*
4444

45+
/** Attachment indicating that an argument in an application was a default arg. */
46+
val UsedDefaults = Property.StickyKey[Unit]
47+
4548
def extractorMember(tp: Type, name: Name)(using Context): SingleDenotation =
4649
tp.member(name).suchThat(sym => sym.info.isParameterless && sym.info.widenExpr.isValueType)
4750

@@ -774,6 +777,14 @@ trait Applications extends Compatibility {
774777
methodType.isImplicitMethod && (applyKind == ApplyKind.Using || failEmptyArgs)
775778

776779
if !defaultArg.isEmpty then
780+
if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled)
781+
&& inferImplicitArg(formal, appPos.span).tpe.isError == false
782+
then
783+
report.warning(DefaultShadowsGiven(methodType.paramNames(n)), appPos)
784+
else if ctx.settings.Whas.recurseWithDefault && ctx.outersIterator.map(_.owner).toSet(sym)
785+
then
786+
defaultArg.withAttachment(UsedDefaults, ()) // might warn at tailrec
787+
777788
defaultArg.tpe.widen match
778789
case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1)
779790
case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1)

tests/neg/19414-desugared.check renamed to tests/neg/i19414-desugared.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------
1+
-- [E172] Type Error: tests/neg/i19414-desugared.scala:22:34 -----------------------------------------------------------
22
22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
33
| ^
44
|No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef.
File renamed without changes.

tests/neg/19414.check renamed to tests/neg/i19414.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- [E172] Type Error: tests/neg/19414.scala:15:34 ----------------------------------------------------------------------
1+
-- [E172] Type Error: tests/neg/i19414.scala:15:34 ---------------------------------------------------------------------
22
15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances
33
| ^
44
|No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef.
File renamed without changes.

tests/warn/i23541.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//> using options -Wtailrec-default
2+
3+
def fun(x: Int)(using p: Int, q: Int = 0): Int =
4+
if x <= 0 then p * q
5+
else fun(x - 1)(using p = p + x) // warn recurse uses default (instead of given passed down the stack)
6+
7+
def gun(x: Int)(p: Int, q: Int = 0): Int =
8+
if x <= 0 then p * q
9+
else gun(x - 1)(p = p + x) // warn recurse uses default (value not passed down the stack)
10+
11+
def nested(using x: Int, y: Int = 42): Int =
12+
def f: Int = nested(using x) // nowarn only self-recursive tailrec is eligible for warning
13+
f
14+
15+
def f(using s: String, i: Int = 1): String = s * i
16+
def g(using s: String)(using i: Int = 1): String = s * i
17+
18+
@main def Test =
19+
println(fun(3)(using p = 0, q = 1))
20+
locally:
21+
given String = "ab"
22+
println(f) // prints "ab"
23+
println(g) // prints "ab"
24+
locally:
25+
println(f(using s = "ab")) // prints "ab"
26+
println(g(using s = "ab")) // prints "ab"
27+
locally:
28+
given Int = 2
29+
println(f(using s = "ab")) // warn uses default instead of given // prints "ab"
30+
println(g(using s = "ab")) // prints "abab"

0 commit comments

Comments
 (0)