diff --git a/src/compiler/scala/tools/reflect/FastStringInterpolator.scala b/src/compiler/scala/tools/reflect/FastStringInterpolator.scala index 7f664aa85b29..0d655b266681 100644 --- a/src/compiler/scala/tools/reflect/FastStringInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FastStringInterpolator.scala @@ -106,11 +106,22 @@ trait FastStringInterpolator extends FormatInterpolator { val emptyLit = treatedContents.isEmpty if (i < numLits - 1) { val arg = argsIndexed(i) - if (linting && !(arg.tpe =:= definitions.StringTpe)) + def warn(msg: String) = runReporting.warning(arg.pos, msg, WFlagTostringInterpolated, c.internal.enclosingOwner) + def stringlyBranches = arg match { + case If(_, thenp, elsep) => thenp.tpe <:< definitions.StringTpe && elsep.tpe <:< definitions.StringTpe + case Match(_, cases) => + cases.forall { + case CaseDef(_, _, body) => body.tpe <:< definitions.StringTpe + } + case _ => false + } + if (linting && !(arg.tpe =:= definitions.StringTpe)) // literals are widened, so include Null, Nothing if (arg.tpe.typeSymbol eq definitions.UnitClass) - runReporting.warning(arg.pos, "interpolated Unit value", WFlagTostringInterpolated, c.internal.enclosingOwner) + warn("interpolated Unit value") + else if ((arg.tpe.typeSymbol eq definitions.AnyClass) && stringlyBranches) + () // if or match assumes expected type Any of interpolated expression, check branches are strings else if (!definitions.isPrimitiveValueType(arg.tpe)) - runReporting.warning(arg.pos, "interpolation uses toString", WFlagTostringInterpolated, c.internal.enclosingOwner) + warn("interpolation uses toString") concatArgs += arg } if (!emptyLit) concatArgs += lit diff --git a/test/files/neg/tostring-interpolated.check b/test/files/neg/tostring-interpolated.check index 13d0527e6745..2de2aecd4e78 100644 --- a/test/files/neg/tostring-interpolated.check +++ b/test/files/neg/tostring-interpolated.check @@ -1,41 +1,42 @@ -tostring-interpolated.scala:7: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.f +tostring-interpolated.scala:8: warning: interpolation uses toString def f = f"$c" // warn ^ -tostring-interpolated.scala:8: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.s +tostring-interpolated.scala:9: warning: interpolation uses toString def s = s"$c" // warn ^ -tostring-interpolated.scala:9: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.r +tostring-interpolated.scala:10: warning: interpolation uses toString def r = raw"$c" // warn ^ -tostring-interpolated.scala:11: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.format +tostring-interpolated.scala:12: warning: interpolation uses toString def format = f"${c.x}%d in $c or $c%s" // warn using c.toString // warn ^ -tostring-interpolated.scala:11: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.format +tostring-interpolated.scala:12: warning: interpolation uses toString def format = f"${c.x}%d in $c or $c%s" // warn using c.toString // warn ^ -tostring-interpolated.scala:13: warning: Boolean format is null test for non-Boolean - def bool = f"$c%b" // warn just a null check - ^ -tostring-interpolated.scala:15: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.oops - def oops = s"${null} slipped thru my fingers" // warn - ^ -tostring-interpolated.scala:20: error: interpolation uses toString -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=T.greeting +tostring-interpolated.scala:17: warning: interpolation uses toString def greeting = s"$sb, world" // warn ^ -tostring-interpolated.scala:31: error: interpolated Unit value -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=Mitigations.unitized +tostring-interpolated.scala:21: warning: Boolean format is null test for non-Boolean + def bool = f"$c%b" // warn just a null check (quirk of Java format) + ^ +tostring-interpolated.scala:23: warning: interpolation uses toString + def oops = s"${null} slipped thru my fingers" // warn although conforms to String + ^ +tostring-interpolated.scala:25: warning: interpolation uses toString + def exceptionally = s"Hello, ${???}" // warn although conforms to String + ^ +tostring-interpolated.scala:36: warning: interpolated Unit value def unitized = s"unfortunately $shown" // warn accidental unit value ^ -tostring-interpolated.scala:32: error: interpolated Unit value -Applicable -Wconf / @nowarn filters for this fatal warning: msg=, cat=w-flag-tostring-interpolated, site=Mitigations.funitized +tostring-interpolated.scala:37: warning: interpolated Unit value def funitized = f"unfortunately $shown" // warn accidental unit value ^ -1 warning -9 errors +tostring-interpolated.scala:53: warning: interpolation uses toString + val greeting = s"Hello ${if (shouldCaps) "WORLD" else world}" // warn + ^ +tostring-interpolated.scala:64: warning: interpolation uses toString + val greeting = s"Hello ${x match { case 42 => "WORLD" case 27 => world case _ => ??? }}" // warn + ^ +error: No warnings can be incurred under -Werror. +13 warnings +1 error diff --git a/test/files/neg/tostring-interpolated.scala b/test/files/neg/tostring-interpolated.scala index 959720e4aacb..5f44897ece39 100644 --- a/test/files/neg/tostring-interpolated.scala +++ b/test/files/neg/tostring-interpolated.scala @@ -1,4 +1,5 @@ -//> using options -Wconf:cat=w-flag-tostring-interpolated:e -Wtostring-interpolated +//> using options -Werror -Wtostring-interpolated +///> using options -Wconf:cat=w-flag-tostring-interpolated:e -Wtostring-interpolated case class C(x: Int) @@ -10,21 +11,25 @@ trait T { def format = f"${c.x}%d in $c or $c%s" // warn using c.toString // warn - def bool = f"$c%b" // warn just a null check - - def oops = s"${null} slipped thru my fingers" // warn - def ok = s"${c.toString}" def sb = new StringBuilder().append("hello") def greeting = s"$sb, world" // warn + + def literally = s"Hello, ${"world"}" // nowarn literal, widened to String + + def bool = f"$c%b" // warn just a null check (quirk of Java format) + + def oops = s"${null} slipped thru my fingers" // warn although conforms to String + + def exceptionally = s"Hello, ${???}" // warn although conforms to String } class Mitigations { val s = "hello, world" val i = 42 - def shown() = println("shown") + def shown = println("shown") // nowarn parenless Unit def because that is at refchecks def ok = s"$s is ok" def jersey = s"number $i" @@ -34,3 +39,34 @@ class Mitigations { def nopct = f"$s is ok" def nofmt = f"number $i" } + +class Branches { + + class C { + val shouldCaps = true + val greeting = s"Hello ${if (shouldCaps) "WORLD" else "world"}" + } + + class D { + val shouldCaps = true + object world { override def toString = "world" } + val greeting = s"Hello ${if (shouldCaps) "WORLD" else world}" // warn + } + + class E { + def x = 42 + val greeting = s"Hello ${x match { case 42 => "WORLD" case 27 => "world" case _ => ??? }}" + } + + class F { + def x = 42 + object world { override def toString = "world" } + val greeting = s"Hello ${x match { case 42 => "WORLD" case 27 => world case _ => ??? }}" // warn + } + + class Z { + val shouldCaps = true + val greeting = s"Hello ${if (shouldCaps) ??? else null}" // nowarn quirk, no string result + } + +}