Skip to content

Commit dde69ce

Browse files
Fix ignored type variable bound warning in type quote pattern (#18199)
See the diff in tests/neg-macros/quote-type-variable-no-inference.check
2 parents 78e7163 + c8a7eee commit dde69ce

11 files changed

+103
-30
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ object desugar {
10141014
* if the type has a pattern variable name
10151015
*/
10161016
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
1017-
assert(ctx.mode.is(Mode.QuotedPattern))
1017+
assert(ctx.mode.isQuotedPattern)
10181018
if tree.name.isVarPattern && !tree.isBackquoted then
10191019
val patternTypeAnnot = New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
10201020
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
@@ -1363,7 +1363,7 @@ object desugar {
13631363
case tree: ValDef => valDef(tree)
13641364
case tree: TypeDef =>
13651365
if (tree.isClassDef) classDef(tree)
1366-
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
1366+
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
13671367
else tree
13681368
case tree: DefDef =>
13691369
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef

compiler/src/dotty/tools/dotc/core/Mode.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ case class Mode(val bits: Int) extends AnyVal {
1010

1111
def isExpr: Boolean = (this & PatternOrTypeBits) == None
1212

13+
/** Are we in the body of quoted pattern? */
14+
def isQuotedPattern: Boolean = (this & QuotedPatternBits) != None
15+
1316
override def toString: String =
1417
(0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")")
1518

@@ -69,6 +72,9 @@ object Mode {
6972
*/
7073
val Printing: Mode = newMode(10, "Printing")
7174

75+
/** Are we in a quote the body of quoted type pattern? */
76+
val QuotedTypePattern: Mode = newMode(11, "QuotedTypePattern")
77+
7278
/** We are currently in a `viewExists` check. In that case, ambiguous
7379
* implicits checks are disabled and we succeed with the first implicit
7480
* found.
@@ -128,8 +134,10 @@ object Mode {
128134
/** Are we trying to find a hidden implicit? */
129135
val FindHiddenImplicits: Mode = newMode(24, "FindHiddenImplicits")
130136

131-
/** Are we in a quote in a pattern? */
132-
val QuotedPattern: Mode = newMode(25, "QuotedPattern")
137+
/** Are we in a quote the body of quoted expression pattern? */
138+
val QuotedExprPattern: Mode = newMode(25, "QuotedExprPattern")
139+
140+
val QuotedPatternBits: Mode = QuotedExprPattern | QuotedTypePattern
133141

134142
/** Are we typechecking the rhs of an extension method? */
135143
val InExtensionMethod: Mode = newMode(26, "InExtensionMethod")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ object TreeChecker {
709709
super.typedQuotePattern(tree, pt)
710710

711711
override def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree =
712-
assert(ctx.mode.is(Mode.QuotedPattern))
712+
assert(ctx.mode.isQuotedPattern)
713713
def isAppliedIdent(rhs: untpd.Tree): Boolean = rhs match
714714
case _: Ident => true
715715
case rhs: GenericApply => isAppliedIdent(rhs.fun)

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

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ trait QuotesAndSplices {
7878
def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = {
7979
record("typedSplice")
8080
checkSpliceOutsideQuote(tree)
81-
assert(!ctx.mode.is(Mode.QuotedPattern))
81+
assert(!ctx.mode.isQuotedPattern)
8282
tree.expr match {
8383
case untpd.Quote(innerExpr, Nil) if innerExpr.isTerm =>
8484
report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos)
@@ -110,8 +110,6 @@ trait QuotesAndSplices {
110110
def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = {
111111
record("typedSplicePattern")
112112
if isFullyDefined(pt, ForceDegree.flipBottom) then
113-
def patternOuterContext(ctx: Context): Context =
114-
if (ctx.mode.is(Mode.QuotedPattern)) patternOuterContext(ctx.outer) else ctx
115113
val typedArgs = withMode(Mode.InQuotePatternHoasArgs) {
116114
tree.args.map {
117115
case arg: untpd.Ident =>
@@ -125,8 +123,7 @@ trait QuotesAndSplices {
125123
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
126124
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
127125
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
128-
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
129-
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(patternOuterContext(ctx).owner))
126+
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(using quotePatternSpliceContext)
130127
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
131128
val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType
132129
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
@@ -145,7 +142,7 @@ trait QuotesAndSplices {
145142
* The prototype must be fully defined to be able to infer the type of `R`.
146143
*/
147144
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
148-
assert(ctx.mode.is(Mode.QuotedPattern))
145+
assert(ctx.mode.isQuotedPattern)
149146
val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked
150147
def isInBraces: Boolean = splice.span.end != splice.body.span.end
151148
if isInBraces then // ${x}(...) match an application
@@ -166,26 +163,29 @@ trait QuotesAndSplices {
166163
val typeSymInfo = pt match
167164
case pt: TypeBounds => pt
168165
case _ => TypeBounds.empty
166+
167+
def warnOnInferredBounds(typeSym: Symbol) =
168+
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
169+
val (openQuote, closeQuote) = if ctx.mode.is(Mode.QuotedExprPattern) then ("'{", "}") else ("'[", "]")
170+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos)
171+
169172
getQuotedPatternTypeVariable(tree.name.asTypeName) match
170173
case Some(typeSym) =>
171174
checkExperimentalFeature(
172175
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
173176
tree.srcPos,
174177
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
175-
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
176-
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym${typeSym.info & typeSymInfo}; ... }`", tree.srcPos)
178+
warnOnInferredBounds(typeSym)
177179
ref(typeSym)
178180
case None =>
179-
def spliceOwner(ctx: Context): Symbol =
180-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
181+
val spliceContext = quotePatternSpliceContext
181182
val name = tree.name.toTypeName
182183
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
183184
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
184-
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
185+
val typeSym = newSymbol(spliceContext.owner, name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
185186
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
186187
addQuotedPatternTypeVariable(typeSym)
187-
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
188-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
188+
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(using spliceContext)
189189
pat.select(tpnme.Underlying)
190190

191191
private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =
@@ -454,7 +454,7 @@ trait QuotesAndSplices {
454454
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
455455

456456
val (typeTypeVariables, patternCtx) =
457-
val quoteCtx = quotePatternContext()
457+
val quoteCtx = quotePatternContext(quoted.isType)
458458
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
459459
else typedBlockStats(untpdTypeVariables)(using quoteCtx)
460460

@@ -543,13 +543,26 @@ object QuotesAndSplices {
543543
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
544544
ctx.property(TypeVariableKey).get.get(name)
545545

546-
/** Get the symbol for the quoted pattern type variable if it exists */
546+
/** Get the symbol for the quoted pattern type variable if it exists */
547547
def addQuotedPatternTypeVariable(sym: Symbol)(using Context): Unit =
548548
ctx.property(TypeVariableKey).get.update(sym.name.asTypeName, sym)
549549

550-
/** Context used to type the contents of a quoted */
551-
def quotePatternContext()(using Context): Context =
550+
/** Context used to type the contents of a quote pattern */
551+
def quotePatternContext(isTypePattern: Boolean)(using Context): Context =
552552
quoteContext.fresh.setNewScope
553-
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
553+
.addMode(if isTypePattern then Mode.QuotedTypePattern else Mode.QuotedExprPattern)
554+
.retractMode(Mode.Pattern) // TODO do we need Mode.QuotedPattern?
554555
.setProperty(TypeVariableKey, collection.mutable.Map.empty)
556+
557+
/** Context used to type the contents of a quote pattern splice */
558+
def quotePatternSpliceContext(using Context): Context =
559+
spliceContext
560+
.retractMode(Mode.QuotedPatternBits)
561+
.addMode(Mode.Pattern)
562+
.withOwner(quotePatternOwner(ctx))
563+
564+
/** First outer context owner that is outside of a quoted pattern context. */
565+
private def quotePatternOwner(ctx: Context): Symbol =
566+
if ctx.mode.isQuotedPattern then quotePatternOwner(ctx.outer) else ctx.owner
567+
555568
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
121121
override def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree =
122122
assertTyped(tree)
123123
val bindings1 = tree.bindings.map(typed(_))
124-
val bodyCtx = quoteContext.retractMode(Mode.Pattern).addMode(Mode.QuotedPattern)
124+
val bodyCtx = quoteContext
125+
.retractMode(Mode.Pattern)
126+
.addMode(if tree.body.isType then Mode.QuotedTypePattern else Mode.QuotedExprPattern)
125127
val body1 = typed(tree.body, promote(tree).bodyType)(using bodyCtx)
126128
val quotes1 = typed(tree.quotes, defn.QuotesClass.typeRef)
127129
untpd.cpy.QuotePattern(tree)(bindings1, body1, quotes1).withType(tree.typeOpt)
@@ -132,7 +134,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
132134
val patternTpe =
133135
if args1.isEmpty then tree.typeOpt
134136
else defn.FunctionType(args1.size).appliedTo(args1.map(_.tpe) :+ tree.typeOpt)
135-
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPattern)
137+
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPatternBits)
136138
val body1 = typed(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patternTpe))(using bodyCtx)
137139
val args = tree.args.mapconserve(typedExpr(_))
138140
untpd.cpy.SplicePattern(tree)(body1, args1).withType(tree.typeOpt)

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
560560
return tree.withType(defn.AnyType)
561561
if untpd.isVarPattern(tree) && name.isTermName then
562562
return typed(desugar.patternVar(tree), pt)
563-
else if ctx.mode.is(Mode.QuotedPattern) then
563+
else if ctx.mode.isQuotedPattern then
564564
if untpd.isVarPattern(tree) && name.isTypeName then
565565
return typedQuotedTypeVar(tree, pt)
566566
end if
@@ -966,7 +966,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
966966
// so the expected type is the union `Seq[T] | Array[_ <: T]`.
967967
val ptArg =
968968
// FIXME(#8680): Quoted patterns do not support Array repeated arguments
969-
if ctx.mode.is(Mode.QuotedPattern) then
969+
if ctx.mode.isQuotedPattern then
970970
pt.translateFromRepeated(toArray = false, translateWildcard = true)
971971
else
972972
pt.translateFromRepeated(toArray = false, translateWildcard = true)
@@ -2221,7 +2221,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22212221
val (desugaredArg, argPt) =
22222222
if ctx.mode.is(Mode.Pattern) then
22232223
(if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparamBounds)
2224-
else if ctx.mode.is(Mode.QuotedPattern) then
2224+
else if ctx.mode.isQuotedPattern then
22252225
(arg, tparamBounds)
22262226
else
22272227
(arg, WildcardType)
@@ -4268,7 +4268,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42684268
tree.tpe.EtaExpand(tp.typeParamSymbols)
42694269
tree.withType(tp1)
42704270
}
4271-
if (ctx.mode.is(Mode.Pattern) || ctx.mode.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1
4271+
if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1
42724272
else err.typeMismatch(tree1, pt)
42734273
}
42744274

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:22 ---------------------------------------------
2+
5 | case '{ $_ : F[t, t]; () } => // warn // error
3+
| ^
4+
| Ignored bound <: Double
5+
|
6+
| Consider defining bounds explicitly:
7+
| '{ type t <: Int & Double; ... }
8+
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:12 --------------------------
9+
5 | case '{ $_ : F[t, t]; () } => // warn // error
10+
| ^
11+
| Type argument t does not conform to upper bound Double in inferred type F[t, t]
12+
|
13+
| longer explanation available when compiling with `-explain`
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted.*
2+
3+
def test2(x: Expr[Any])(using Quotes) =
4+
x match
5+
case '{ $_ : F[t, t]; () } => // warn // error
6+
case '{ type u <: Int & Double; $_ : F[u, u] } =>
7+
8+
type F[X <: Int, Y <: Double]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:5:22 ---------------------------------------------
2+
5 | case '{ $_ : F[t, t]; () } => // warn // error
3+
| ^
4+
| Ignored bound <: Comparable[U]
5+
|
6+
| Consider defining bounds explicitly:
7+
| '{ type t <: Comparable[U]; ... }
8+
-- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:6:49 ---------------------------------------------
9+
6 | case '{ type u <: Comparable[`u`]; $_ : F[u, u] } =>
10+
| ^
11+
| Ignored bound <: Comparable[Any]
12+
|
13+
| Consider defining bounds explicitly:
14+
| '{ type u <: Comparable[u] & Comparable[Any]; ... }
15+
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-3.scala:5:12 --------------------------
16+
5 | case '{ $_ : F[t, t]; () } => // warn // error
17+
| ^
18+
| Type argument t does not conform to upper bound Comparable[t] in inferred type F[t, t]
19+
|
20+
| longer explanation available when compiling with `-explain`
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted.*
2+
3+
def test2(x: Expr[Any])(using Quotes) =
4+
x match
5+
case '{ $_ : F[t, t]; () } => // warn // error
6+
case '{ type u <: Comparable[`u`]; $_ : F[u, u] } =>
7+
8+
type F[T, U <: Comparable[U]]

tests/neg-macros/quote-type-variable-no-inference.check

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
| ^
44
| Ignored bound <: Double
55
|
6-
| Consider defining bounds explicitly `'{ type t <: Int & Double; ... }`
6+
| Consider defining bounds explicitly:
7+
| '[ type t <: Int & Double; ... ]
78
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference.scala:5:9 -----------------------------
89
5 | case '[ F[t, t] ] => // warn // error // error
910
| ^

0 commit comments

Comments
 (0)