Skip to content

Commit 7fbd30b

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 dde69ce commit 7fbd30b

File tree

3 files changed

+94
-3
lines changed

3 files changed

+94
-3
lines changed

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,38 @@ trait QuotesAndSplices {
164164
case pt: TypeBounds => pt
165165
case _ => TypeBounds.empty
166166

167-
def warnOnInferredBounds(typeSym: Symbol) =
167+
def warnOnInferredBounds(typeSym: Symbol, ignoredBounds: Boolean) =
168168
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
169169
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)
170+
if ignoredBounds then
171+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos)
172+
else
173+
// TODO only keep this branch this guard once SIP-53 is non-experimental
174+
val extra =
175+
if ctx.mode.is(Mode.QuotedExprPattern) then ""
176+
else s"""
177+
|Explicit type pattern in quoted type patterns 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+
|""".stripMargin
181+
182+
report.warning(em"Type variable `$tree` has partially inferred bounds$typeSymInfo.\n\nConsider defining bounds explicitly:\n $openQuote $typeSym$typeSymInfo; ... $closeQuote$extra", tree.srcPos)
171183

172184
getQuotedPatternTypeVariable(tree.name.asTypeName) match
173185
case Some(typeSym) =>
174186
checkExperimentalFeature(
175187
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
176188
tree.srcPos,
177189
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
178-
warnOnInferredBounds(typeSym)
190+
warnOnInferredBounds(typeSym, ignoredBounds = true)
179191
ref(typeSym)
180192
case None =>
181193
val spliceContext = quotePatternSpliceContext
182194
val name = tree.name.toTypeName
183195
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
184196
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
185197
val typeSym = newSymbol(spliceContext.owner, name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
198+
warnOnInferredBounds(typeSym, ignoredBounds = false)
186199
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
187200
addQuotedPatternTypeVariable(typeSym)
188201
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(using spliceContext)
@@ -536,8 +549,12 @@ trait QuotesAndSplices {
536549
object QuotesAndSplices {
537550
import tpd._
538551

552+
private enum QuotePattenKind:
553+
case Expr, Type
554+
539555
/** Key for mapping from quoted pattern type variable names into their symbol */
540556
private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]]
557+
private val QuotePattenKindKey = new Property.Key[QuotePattenKind]
541558

542559
/** Get the symbol for the quoted pattern type variable if it exists */
543560
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
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

0 commit comments

Comments
 (0)