Skip to content

Commit c8a7eee

Browse files
committed
Fix ignored type variable bound warning in type quote pattern
1 parent 766d1cf commit c8a7eee

11 files changed

+104
-31
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,7 @@ object desugar {
10101010
* if the type has a pattern variable name
10111011
*/
10121012
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
1013-
assert(ctx.mode.is(Mode.QuotedPattern))
1013+
assert(ctx.mode.isQuotedPattern)
10141014
if tree.name.isVarPattern && !tree.isBackquoted then
10151015
val patternTypeAnnot = New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
10161016
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
@@ -1359,7 +1359,7 @@ object desugar {
13591359
case tree: ValDef => valDef(tree)
13601360
case tree: TypeDef =>
13611361
if (tree.isClassDef) classDef(tree)
1362-
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
1362+
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
13631363
else tree
13641364
case tree: DefDef =>
13651365
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
@@ -707,7 +707,7 @@ object TreeChecker {
707707
super.typedQuotePattern(tree, pt)
708708

709709
override def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree =
710-
assert(ctx.mode.is(Mode.QuotedPattern))
710+
assert(ctx.mode.isQuotedPattern)
711711
def isAppliedIdent(rhs: untpd.Tree): Boolean = rhs match
712712
case _: Ident => true
713713
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
@@ -114,7 +114,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
114114
override def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree =
115115
assertTyped(tree)
116116
val bindings1 = tree.bindings.map(typed(_))
117-
val bodyCtx = quoteContext.retractMode(Mode.Pattern).addMode(Mode.QuotedPattern)
117+
val bodyCtx = quoteContext
118+
.retractMode(Mode.Pattern)
119+
.addMode(if tree.body.isType then Mode.QuotedTypePattern else Mode.QuotedExprPattern)
118120
val body1 = typed(tree.body, promote(tree).bodyType)(using bodyCtx)
119121
val quotes1 = typed(tree.quotes, defn.QuotesClass.typeRef)
120122
untpd.cpy.QuotePattern(tree)(bindings1, body1, quotes1).withType(tree.typeOpt)
@@ -125,7 +127,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
125127
val patternTpe =
126128
if args1.isEmpty then tree.typeOpt
127129
else defn.FunctionType(args1.size).appliedTo(args1.map(_.tpe) :+ tree.typeOpt)
128-
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPattern)
130+
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPatternBits)
129131
val body1 = typed(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patternTpe))(using bodyCtx)
130132
val args = tree.args.mapconserve(typedExpr(_))
131133
untpd.cpy.SplicePattern(tree)(body1, args1).withType(tree.typeOpt)

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

Lines changed: 5 additions & 5 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
@@ -964,7 +964,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
964964
// so the expected type is the union `Seq[T] | Array[_ <: T]`.
965965
val ptArg =
966966
// FIXME(#8680): Quoted patterns do not support Array repeated arguments
967-
if ctx.mode.is(Mode.QuotedPattern) then
967+
if ctx.mode.isQuotedPattern then
968968
pt.translateFromRepeated(toArray = false, translateWildcard = true)
969969
else
970970
pt.translateFromRepeated(toArray = false, translateWildcard = true)
@@ -2225,7 +2225,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22252225
val (desugaredArg, argPt) =
22262226
if ctx.mode.is(Mode.Pattern) then
22272227
(if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparamBounds)
2228-
else if ctx.mode.is(Mode.QuotedPattern) then
2228+
else if ctx.mode.isQuotedPattern then
22292229
(arg, tparamBounds)
22302230
else
22312231
(arg, WildcardType)
@@ -3957,7 +3957,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
39573957
sym.isConstructor
39583958
|| sym.matchNullaryLoosely
39593959
|| Feature.warnOnMigration(msg, tree.srcPos, version = `3.0`)
3960-
&& {
3960+
&& {
39613961
msg.actions
39623962
.headOption
39633963
.foreach(Rewrites.applyAction)
@@ -4274,7 +4274,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42744274
tree.tpe.EtaExpand(tp.typeParamSymbols)
42754275
tree.withType(tp1)
42764276
}
4277-
if (ctx.mode.is(Mode.Pattern) || ctx.mode.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1
4277+
if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1
42784278
else err.typeMismatch(tree1, pt)
42794279
}
42804280

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)