Skip to content

Backport "Warn if interpolator uses toString" to 3.3 LTS #350

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

Merged
merged 2 commits into from
Apr 28, 2025
Merged
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
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@ object Completion:
case _ => None

private object StringContextApplication:
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
def unapply(path: List[tpd.Tree]): Option[tpd.Apply] =
path match
case tpd.Select(qual @ tpd.Apply(tpd.Select(tpd.Select(_, StdNames.nme.StringContext), _), _), _) :: _ =>
case tpd.Select(qual @ tpd.Apply(tpd.Select(tpd.Select(_, nme.StringContext), _), _), _) :: _ =>
Some(qual)
case _ => None


/** Inspect `path` to determine the offset where the completion result should be inserted. */
def completionOffset(untpdPath: List[untpd.Tree]): Int =
Expand Down Expand Up @@ -229,8 +229,8 @@ object Completion:
// See example in dotty.tools.languageserver.CompletionTest.syntheticThis
case tpd.Select(qual @ tpd.This(_), _) :: _ if qual.span.isSynthetic => completer.scopeCompletions
case StringContextApplication(qual) =>
completer.scopeCompletions ++ completer.selectionCompletions(qual)
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
completer.scopeCompletions ++ completer.selectionCompletions(qual)
case tpd.Select(qual, _) :: _ if qual.typeOpt.hasSimpleKind =>
completer.selectionCompletions(qual)
case tpd.Select(qual, _) :: _ => Map.empty
case (tree: tpd.ImportOrExport) :: _ => completer.directMemberCompletions(tree.expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case EnumMayNotBeValueClassesID // errorNumber: 206
case IllegalUnrollPlacementID // errorNumber: 207 - unused in LTS
case ExtensionHasDefaultID // errorNumber: 208
case FormatInterpolationErrorID // errorNumber: 209

def errorNumber = ordinal - 1

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/reporting/MessageKind.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum MessageKind:
case PotentialIssue
case UnusedSymbol
case Staging
case Interpolation

/** Human readable message that will end up being shown to the user.
* NOTE: This is only used in the situation where you have multiple words
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3208,3 +3208,8 @@ final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxM

def explain(using Context) = ""
end EnumMayNotBeValueClasses

class BadFormatInterpolation(errorText: String)(using Context) extends Message(FormatInterpolationErrorID):
def kind = MessageKind.Interpolation
def msg(using Context) = errorText
def explain(using Context) = ""
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Phases.typerPhase
import dotty.tools.dotc.reporting.BadFormatInterpolation
import dotty.tools.dotc.util.Spans.Span
import dotty.tools.dotc.util.chaining.*

Expand Down Expand Up @@ -276,10 +277,16 @@ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List
val pos = partsElems(index).sourcePos
val bgn = pos.span.start + offset
val fin = if end < 0 then pos.span.end else pos.span.start + end
pos.withSpan(Span(bgn, fin, bgn))
pos.withSpan(Span(start = bgn, end = fin, point = bgn))

extension (r: report.type)
def argError(message: String, index: Int): Unit = r.error(message, args(index).srcPos).tap(_ => reported = true)
def partError(message: String, index: Int, offset: Int, end: Int = -1): Unit = r.error(message, partPosAt(index, offset, end)).tap(_ => reported = true)
def partWarning(message: String, index: Int, offset: Int, end: Int = -1): Unit = r.warning(message, partPosAt(index, offset, end)).tap(_ => reported = true)
def argError(message: String, index: Int): Unit =
r.error(BadFormatInterpolation(message), args(index).srcPos)
.tap(_ => reported = true)
def partError(message: String, index: Int, offset: Int, end: Int = -1): Unit =
r.error(BadFormatInterpolation(message), partPosAt(index, offset, end))
.tap(_ => reported = true)
def partWarning(message: String, index: Int, offset: Int, end: Int): Unit =
r.warning(BadFormatInterpolation(message), partPosAt(index, offset, end))
.tap(_ => reported = true)
end TypedFormatChecker
92 changes: 46 additions & 46 deletions tests/neg/f-interpolator-neg.check

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions tests/neg/f-interpolator-tests.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- Error: tests/neg/f-interpolator-tests.scala:4:19 --------------------------------------------------------------------
-- [E209] Interpolation Error: tests/neg/f-interpolator-tests.scala:4:19 -----------------------------------------------
4 | def `um uh` = f"$s%d" // error
| ^
| Found: (T.this.s : String), Required: Int, Long, Byte, Short, BigInt
-- Error: tests/neg/f-interpolator-tests.scala:10:10 -------------------------------------------------------------------
-- [E209] Interpolation Error: tests/neg/f-interpolator-tests.scala:10:10 ----------------------------------------------
10 | f"x % y = ${x % y}%d" // error: illegal conversion character 'y'
| ^
| illegal conversion character 'y'
2 changes: 1 addition & 1 deletion tests/neg/fEscapes.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-- Error: tests/neg/fEscapes.scala:5:18 --------------------------------------------------------------------------------
-- [E209] Interpolation Error: tests/neg/fEscapes.scala:5:18 -----------------------------------------------------------
5 | val fEscape = f"\u$octal%04x" // error
| ^^
| invalid unicode escape at index 1 of \u