Skip to content

Commit d2320ea

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 d2320ea

File tree

3 files changed

+118
-10
lines changed

3 files changed

+118
-10
lines changed

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

Lines changed: 46 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,40 @@ 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+
s"""This bound pattern is not supported yet.
176+
|This kind of pattern will be supported with SIP-53.
177+
|SIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html
178+
|""".stripMargin, tree.srcPos)
179+
else
180+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos)
181+
167182
getQuotedPatternTypeVariable(tree.name.asTypeName) match
168183
case Some(typeSym) =>
169184
checkExperimentalFeature(
170185
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
171186
tree.srcPos,
172187
"\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)
188+
warnOnInferredBounds(typeSym, ignoredBounds = true)
175189
ref(typeSym)
176190
case None =>
191+
177192
def spliceOwner(ctx: Context): Symbol =
178193
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
179194
val name = tree.name.toTypeName
180195
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
181196
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
182197
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
198+
warnOnInferredBounds(typeSym, ignoredBounds = false)
183199
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
184200
addQuotedPatternTypeVariable(typeSym)
185201
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
@@ -452,7 +468,7 @@ trait QuotesAndSplices {
452468
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
453469

454470
val (typeTypeVariables, patternCtx) =
455-
val quoteCtx = quotePatternContext()
471+
val quoteCtx = quotePatternContext(quoted.isType)
456472
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
457473
else typedBlockStats(untpdTypeVariables)(using quoteCtx)
458474

@@ -534,20 +550,40 @@ trait QuotesAndSplices {
534550
object QuotesAndSplices {
535551
import tpd._
536552

553+
private enum QuotePattenKind:
554+
case Expr, Type
555+
537556
/** Key for mapping from quoted pattern type variable names into their symbol */
538557
private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]]
558+
private val QuotePattenKindKey = new Property.Key[QuotePattenKind]
539559

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

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

548-
/** Context used to type the contents of a quoted */
549-
def quotePatternContext()(using Context): Context =
574+
/** Context used to type the contents of a quote pattern */
575+
def quotePatternContext(isTypePattern: Boolean)(using Context): Context =
550576
quoteContext.fresh.setNewScope
551-
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
577+
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern) // TODO do we need Mode.QuotedPattern?
552578
.setProperty(TypeVariableKey, collection.mutable.Map.empty)
579+
.setProperty(QuotePattenKindKey, if isTypePattern then QuotePattenKind.Type else QuotePattenKind.Expr)
580+
581+
/** Context used to type the contents of a quote pattern splice */
582+
def quotePatternSpliceContext(using Context): Context =
583+
def patternOuterContext(ctx: Context): Context =
584+
if ctx.mode.is(Mode.QuotedPattern) then patternOuterContext(ctx.outer) else ctx
585+
spliceContext
586+
.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern)
587+
.dropProperty(QuotePattenKindKey)
588+
.withOwner(patternOuterContext(ctx).owner)
553589
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
| This bound pattern is not supported yet.
47+
| This kind of pattern will be supported with SIP-53.
48+
| 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

0 commit comments

Comments
 (0)