diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 63 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/StdNames.scala | 21 | ||||
-rw-r--r-- | test/files/neg/forgot-interpolator.check | 11 | ||||
-rw-r--r-- | test/files/neg/forgot-interpolator.scala | 34 |
4 files changed, 85 insertions, 44 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index e0ea555fe8..2a2fd889ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -96,7 +96,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper private final val SYNTHETIC_PRIVATE = TRANS_FLAG private final val InterpolatorCodeRegex = """\$\{.*?\}""".r - private final val InterpolatorIdentRegex = """\$\w+""".r + private final val InterpolatorIdentRegex = """\$[$\w]+""".r // note that \w doesn't include $ abstract class Typer(context0: Context) extends TyperDiagnostics with Adaptation with Tag with PatternTyper with TyperContextErrors { import context0.unit @@ -4878,42 +4878,39 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } // Warn about likely interpolated strings which are missing their interpolators - def warnMissingInterpolator(tree: Literal) = if (!isPastTyper) { + def warnMissingInterpolator(lit: Literal): Unit = if (!isPastTyper) { // attempt to avoid warning about the special interpolated message string // for implicitNotFound or any standard interpolation (with embedded $$). - def isArgToCertainApply = context.enclosingApply.tree match { - case Apply(Select(Apply(Ident(n), parts), _), args) if n.toTypeName == StringContextClass.name - => true // parts contains tree - case Apply(Select(New(Ident(n)), _), _) if n == ImplicitNotFoundClass.name - => true - case _ => false - } - def warnAbout(s: String) = { - def names = InterpolatorIdentRegex findAllIn s map (n => TermName(n stripPrefix "$")) - def isSuspiciousExpr = (InterpolatorCodeRegex findFirstIn s).nonEmpty - //def isSuspiciousName = names exists (lookUp _ andThen isCandidate _) - def suspiciousName = names find (n => isCandidate(lookUp(n))) - def lookUp(n: TermName) = context.lookupSymbol(n, _ => true).symbol - def isCandidate(s: Symbol) = s.exists && s.alternatives.exists(alt => !symRequiresArg(alt)) - def symRequiresArg(s: Symbol) = ( - s.paramss.nonEmpty - && (s.paramss.head.headOption filterNot (_.isImplicit)).isDefined - ) - val suggest = "Did you forget the interpolator?" - if (isSuspiciousExpr) - unit.warning(tree.pos, s"That looks like an interpolated expression! $suggest") - else /* if (isSuspiciousName) */ suspiciousName foreach (n => - unit.warning(tree.pos, s"`$$$n` looks like an interpolated identifier! $suggest") + def isRecognizablyNotForInterpolation = context.enclosingApply.tree match { + case Apply(Select(Apply(RefTree(_, nme.StringContext), _), _), _) => true + case Apply(Select(New(RefTree(_, tpnme.implicitNotFound)), _), _) => true + case _ => false + } + def requiresNoArgs(tp: Type): Boolean = tp match { + case PolyType(_, restpe) => requiresNoArgs(restpe) + case MethodType(Nil, restpe) => requiresNoArgs(restpe) // may be a curried method - can't tell yet + case MethodType(p :: _, _) => p.isImplicit // implicit method requires no args + case _ => true // catches all others including NullaryMethodType + } + def isPlausible(m: Symbol) = m.alternatives exists (m => requiresNoArgs(m.info)) + + def maybeWarn(s: String): Unit = { + def warn(message: String) = context.unit.warning(lit.pos, s"$message Did you forget the interpolator?") + def suspiciousSym(name: TermName) = context.lookupSymbol(name, _ => true).symbol + def suspiciousExpr = InterpolatorCodeRegex findFirstIn s + def suspiciousIdents = InterpolatorIdentRegex findAllIn s map (s => suspiciousSym(s drop 1)) + + // heuristics - no warning on e.g. a string with only "$asInstanceOf" + if (s contains ' ') ( + if (suspiciousExpr.nonEmpty) + warn("That looks like an interpolated expression!") // "${...}" + else + suspiciousIdents find isPlausible foreach (sym => warn(s"`$$${sym.name}` looks like an interpolated identifier!")) // "$id" ) } - tree.value match { - case Constant(s: String) => - val noWarn = ( - isArgToCertainApply - || !(s contains ' ') // another heuristic - e.g. a string with only "$asInstanceOf" - ) - if (!noWarn) warnAbout(s) - case _ => + lit match { + case Literal(Constant(s: String)) if !isRecognizablyNotForInterpolation => maybeWarn(s) + case _ => } } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index dc5c6704bc..4990596839 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -122,15 +122,16 @@ trait StdNames { final val Unit: NameType = "Unit" // some types whose companions we utilize - final val AnyRef: NameType = "AnyRef" - final val Array: NameType = "Array" - final val List: NameType = "List" - final val Seq: NameType = "Seq" - final val Symbol: NameType = "Symbol" - final val WeakTypeTag: NameType = "WeakTypeTag" - final val TypeTag : NameType = "TypeTag" - final val Expr: NameType = "Expr" - final val String: NameType = "String" + final val AnyRef: NameType = "AnyRef" + final val Array: NameType = "Array" + final val List: NameType = "List" + final val Seq: NameType = "Seq" + final val Symbol: NameType = "Symbol" + final val WeakTypeTag: NameType = "WeakTypeTag" + final val TypeTag : NameType = "TypeTag" + final val Expr: NameType = "Expr" + final val String: NameType = "String" + final val StringContext: NameType = "StringContext" // fictions we use as both types and terms final val ERROR: NameType = "<error>" @@ -237,6 +238,7 @@ trait StdNames { final val ClassManifest: NameType = "ClassManifest" final val Enum: NameType = "Enum" final val Group: NameType = "Group" + final val implicitNotFound: NameType = "implicitNotFound" final val Name: NameType = "Name" final val Tree: NameType = "Tree" final val TermName: NameType = "TermName" @@ -600,7 +602,6 @@ trait StdNames { val RootClass: NameType = "RootClass" val Select: NameType = "Select" val SelectFromTypeTree: NameType = "SelectFromTypeTree" - val StringContext: NameType = "StringContext" val SyntacticApplied: NameType = "SyntacticApplied" val SyntacticAssign: NameType = "SyntacticAssign" val SyntacticBlock: NameType = "SyntacticBlock" diff --git a/test/files/neg/forgot-interpolator.check b/test/files/neg/forgot-interpolator.check index 98440fe657..157cbb4802 100644 --- a/test/files/neg/forgot-interpolator.check +++ b/test/files/neg/forgot-interpolator.check @@ -16,6 +16,15 @@ forgot-interpolator.scala:42: warning: `$bar` looks like an interpolated identif forgot-interpolator.scala:47: warning: `$hippo` looks like an interpolated identifier! Did you forget the interpolator? def h = "$hippo takes an implicit" // warn 6 ^ +forgot-interpolator.scala:88: warning: `$groucho` looks like an interpolated identifier! Did you forget the interpolator? + def f2 = "I salute $groucho" // warn 7 + ^ +forgot-interpolator.scala:89: warning: `$dingo` looks like an interpolated identifier! Did you forget the interpolator? + def f3 = "I even salute $dingo" // warn 8 + ^ +forgot-interpolator.scala:90: warning: `$calico` looks like an interpolated identifier! Did you forget the interpolator? + def f4 = "I also salute $calico" // warn 9 + ^ error: No warnings can be incurred under -Xfatal-warnings. -6 warnings found +9 warnings found one error found diff --git a/test/files/neg/forgot-interpolator.scala b/test/files/neg/forgot-interpolator.scala index e007f15009..34a7c7aef4 100644 --- a/test/files/neg/forgot-interpolator.scala +++ b/test/files/neg/forgot-interpolator.scala @@ -57,3 +57,37 @@ package test { @implicitNotFound("No Z in ${A}") // no warn class Z[A] } + + +package inf1 { + import scala.annotation.implicitNotFound + + @implicitNotFound(msg = "Cannot construct a collection of type ${To} with elements of type ${Elem} based on a collection of type ${From}.") // no warn + trait CannotBuildFrom[-From, -Elem, +To] +} + +package inf2 { + @scala.annotation.implicitNotFound(msg = "Cannot construct a collection of type ${To} with elements of type ${Elem} based on a collection of type ${From}.") // no warn + trait CannotBuildFrom[-From, -Elem, +To] +} + +package inf3 { + @scala.annotation.implicitNotFound("Cannot construct a collection of type ${To} with elements of type ${Elem} based on a collection of type ${From}.") // no warn + trait CannotBuildFrom[-From, -Elem, +To] +} + +package curry { + class A { + def bunko()(x: Int): Int = 5 + def groucho(): Int = 5 + def dingo()()()()()(): Int = 5 // kind of nuts this can be evaluated with just 'dingo', but okay + def calico[T1, T2]()()(): Int = 5 // even nutsier + def palomino[T1, T2]()(y: Int = 5)(): Int = 5 // even nutsier + + def f1 = "I was picked up by the $bunko squad" // no warn + def f2 = "I salute $groucho" // warn 7 + def f3 = "I even salute $dingo" // warn 8 + def f4 = "I also salute $calico" // warn 9 + def f5 = "I draw the line at $palomino" // no warn + } +} |