diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 5cdc742fa753..18cdc82dc101 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -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", @@ -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 */ diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 2dedcd788ec4..e7a1dc14a95d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index eb5e692b2915..d03c0480b8f6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -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." diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 08c2c6a015c0..690a180e52ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -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 @@ -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) { tailrec.println("Rewriting tail recursive call: " + tree.span) rewrote = true diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index da6b20b75c29..290e061772e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 30a5ac22be0d..24473c874c96 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -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 => @@ -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 => @@ -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) => @@ -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) => @@ -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 diff --git a/tests/neg/19414-desugared.check b/tests/neg/i19414-desugared.check similarity index 91% rename from tests/neg/19414-desugared.check rename to tests/neg/i19414-desugared.check index cc51ee471553..72a3a5eabd37 100644 --- a/tests/neg/19414-desugared.check +++ b/tests/neg/i19414-desugared.check @@ -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. diff --git a/tests/neg/19414-desugared.scala b/tests/neg/i19414-desugared.scala similarity index 100% rename from tests/neg/19414-desugared.scala rename to tests/neg/i19414-desugared.scala diff --git a/tests/neg/19414.check b/tests/neg/i19414.check similarity index 91% rename from tests/neg/19414.check rename to tests/neg/i19414.check index 016e3942c825..10bc939494c6 100644 --- a/tests/neg/19414.check +++ b/tests/neg/i19414.check @@ -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. diff --git a/tests/neg/19414.scala b/tests/neg/i19414.scala similarity index 100% rename from tests/neg/19414.scala rename to tests/neg/i19414.scala diff --git a/tests/warn/i23541.check b/tests/warn/i23541.check new file mode 100644 index 000000000000..935c26ffec13 --- /dev/null +++ b/tests/warn/i23541.check @@ -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` diff --git a/tests/warn/i23541.scala b/tests/warn/i23541.scala new file mode 100644 index 000000000000..7f374b421ba3 --- /dev/null +++ b/tests/warn/i23541.scala @@ -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"