Skip to content

Commit 42dee9f

Browse files
committed
Warn on use of inferred quote type variable bounds
This kind of inference is not reliable. We can only consider the bounds from type constructor where the type variable is defined but any other constraints are ignored. Therefore it is not possible to properly infer the type bounds of the type variable. The solution is simple, write the bounds explicitly and just check that those bounds conform to the use site of the type variable.
1 parent a553539 commit 42dee9f

File tree

4 files changed

+125
-11
lines changed

4 files changed

+125
-11
lines changed

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

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = tree.args.map {
116114
case arg: untpd.Ident =>
117115
typedExpr(arg)
@@ -124,7 +122,7 @@ trait QuotesAndSplices {
124122
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
125123
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
126124
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
127-
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(patternOuterContext(ctx).owner))
125+
using quotePatternSpliceContext)
128126
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
129127
val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType
130128
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
@@ -164,22 +162,42 @@ trait QuotesAndSplices {
164162
val typeSymInfo = pt match
165163
case pt: TypeBounds => pt
166164
case _ => TypeBounds.empty
165+
166+
def warnOnInferredBounds(typeSym: Symbol, ignoredBounds: Boolean) =
167+
if !(typeSymInfo =:= TypeBounds.empty) then
168+
val (openQuote, closeQuote) = if isInQuotedExprPattern then ("'{", "}") else ("'[", "]")
169+
if !ignoredBounds then
170+
if !isInQuotedTypePattern then // TODO remove this guard once SIP-53 is non-experimental
171+
report.warning(em"Type variable `$tree` has partially inferred bounds$typeSymInfo.\n\nConsider defining bounds explicitly:\n $openQuote $typeSym$typeSymInfo; ... $closeQuote", tree.srcPos)
172+
else if !(typeSym.info <:< typeSymInfo) then
173+
if isInQuotedTypePattern then // TODO remove this branch once SIP-53 is non-experimental
174+
report.warning(
175+
em"""Ignored bound$typeSymInfo
176+
|
177+
|This bound pattern is not supported yet.
178+
|This kind of pattern will be supported with SIP-53.
179+
|SIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html
180+
|""", tree.srcPos)
181+
else
182+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos)
183+
167184
getQuotedPatternTypeVariable(tree.name.asTypeName) match
168185
case Some(typeSym) =>
169186
checkExperimentalFeature(
170187
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
171188
tree.srcPos,
172189
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
173-
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
174-
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym${typeSym.info & typeSymInfo}; ... }`", tree.srcPos)
190+
warnOnInferredBounds(typeSym, ignoredBounds = true)
175191
ref(typeSym)
176192
case None =>
193+
177194
def spliceOwner(ctx: Context): Symbol =
178195
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
179196
val name = tree.name.toTypeName
180197
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
181198
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
182199
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
200+
warnOnInferredBounds(typeSym, ignoredBounds = false)
183201
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
184202
addQuotedPatternTypeVariable(typeSym)
185203
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
@@ -452,7 +470,7 @@ trait QuotesAndSplices {
452470
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
453471

454472
val (typeTypeVariables, patternCtx) =
455-
val quoteCtx = quotePatternContext()
473+
val quoteCtx = quotePatternContext(quoted.isType)
456474
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
457475
else typedBlockStats(untpdTypeVariables)(using quoteCtx)
458476

@@ -534,20 +552,40 @@ trait QuotesAndSplices {
534552
object QuotesAndSplices {
535553
import tpd._
536554

555+
private enum QuotePattenKind:
556+
case Expr, Type
557+
537558
/** Key for mapping from quoted pattern type variable names into their symbol */
538559
private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]]
560+
private val QuotePattenKindKey = new Property.Key[QuotePattenKind]
539561

540562
/** Get the symbol for the quoted pattern type variable if it exists */
541563
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
542564
ctx.property(TypeVariableKey).get.get(name)
543565

544-
/** Get the symbol for the quoted pattern type variable if it exists */
566+
def isInQuotedExprPattern(using Context): Boolean =
567+
ctx.property(QuotePattenKindKey).contains(QuotePattenKind.Expr)
568+
569+
def isInQuotedTypePattern(using Context): Boolean =
570+
ctx.property(QuotePattenKindKey).contains(QuotePattenKind.Type)
571+
572+
/** Get the symbol for the quoted pattern type variable if it exists */
545573
def addQuotedPatternTypeVariable(sym: Symbol)(using Context): Unit =
546574
ctx.property(TypeVariableKey).get.update(sym.name.asTypeName, sym)
547575

548-
/** Context used to type the contents of a quoted */
549-
def quotePatternContext()(using Context): Context =
576+
/** Context used to type the contents of a quote pattern */
577+
def quotePatternContext(isTypePattern: Boolean)(using Context): Context =
550578
quoteContext.fresh.setNewScope
551-
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
579+
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern) // TODO do we need Mode.QuotedPattern?
552580
.setProperty(TypeVariableKey, collection.mutable.Map.empty)
581+
.setProperty(QuotePattenKindKey, if isTypePattern then QuotePattenKind.Type else QuotePattenKind.Expr)
582+
583+
/** Context used to type the contents of a quote pattern splice */
584+
def quotePatternSpliceContext(using Context): Context =
585+
def patternOuterContext(ctx: Context): Context =
586+
if ctx.mode.is(Mode.QuotedPattern) then patternOuterContext(ctx.outer) else ctx
587+
spliceContext
588+
.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern)
589+
.dropProperty(QuotePattenKindKey)
590+
.withOwner(patternOuterContext(ctx).owner)
553591
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:11:18 ----------------------------------
2+
11 | case '{ $x: C[t] } => // error
3+
| ^
4+
| Type variable `t` has partially inferred bounds <: Int.
5+
|
6+
| Consider defining bounds explicitly:
7+
| '{ type t <: Int; ... }
8+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:12:18 ----------------------------------
9+
12 | case '{ $x: D[t] } => // error
10+
| ^
11+
| Type variable `t` has partially inferred bounds >: Null <: String.
12+
|
13+
| Consider defining bounds explicitly:
14+
| '{ type t >: Null <: String; ... }
15+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:15:18 ----------------------------------
16+
15 | case '{ $x: E[t, t] } => // error
17+
| ^
18+
| Type variable `t` has partially inferred bounds <: Int.
19+
|
20+
| Consider defining bounds explicitly:
21+
| '{ type t <: Int; ... }
22+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:17:18 ----------------------------------
23+
17 | case '{ $x: F[t, t] } => // error // error
24+
| ^
25+
| Type variable `t` has partially inferred bounds <: Int.
26+
|
27+
| Consider defining bounds explicitly:
28+
| '{ type t <: Int; ... }
29+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:17:21 ----------------------------------
30+
17 | case '{ $x: F[t, t] } => // error // error
31+
| ^
32+
| Ignored bound <: String
33+
|
34+
| Consider defining bounds explicitly:
35+
| '{ type t <: Int & String; ... }
36+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:18:36 ----------------------------------
37+
18 | case '{ type t <: Int; $x: F[t, t] } => // error
38+
| ^
39+
| Ignored bound <: String
40+
|
41+
| Consider defining bounds explicitly:
42+
| '{ type t <: Int & String; ... }
43+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:24:17 ----------------------------------
44+
24 | case '[ F[t, t] ] => // error // will have a second error with SIP-53
45+
| ^
46+
| Ignored bound <: String
47+
|
48+
| This bound pattern is not supported yet.
49+
| This kind of pattern will be supported with SIP-53.
50+
| SIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.quoted.*
2+
3+
class C[T <: Int]
4+
class D[T >: Null <: String]
5+
class E[T <: Int, U <: Int]
6+
class F[T <: Int, U <: String]
7+
8+
def test[T: Type](e: Expr[Any])(using Quotes) =
9+
e match
10+
case '{ $x: t } =>
11+
case '{ $x: C[t] } => // error
12+
case '{ $x: D[t] } => // error
13+
case '{ type t <: Int; $x: C[t] } =>
14+
15+
case '{ $x: E[t, t] } => // error
16+
17+
case '{ $x: F[t, t] } => // error // error
18+
case '{ type t <: Int; $x: F[t, t] } => // error
19+
20+
Type.of[T] match
21+
case '[ C[t] ] => // will have an error with SIP-53
22+
case '[ D[t] ] => // will have an error with SIP-53
23+
case '[ E[t, t] ] => // will have an error with SIP-53
24+
case '[ F[t, t] ] => // error // will have a second error with SIP-53

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
| ^
44
| Ignored bound <: Double
55
|
6-
| Consider defining bounds explicitly `'{ type t <: Int & Double; ... }`
6+
| This bound pattern is not supported yet.
7+
| This kind of pattern will be supported with SIP-53.
8+
| SIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html
79
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference.scala:5:9 -----------------------------
810
5 | case '[ F[t, t] ] => // warn // error // error
911
| ^

0 commit comments

Comments
 (0)