Skip to content

Commit e47f798

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 e47f798

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

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

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

167-
def warnOnInferredBounds(typeSym: Symbol) =
167+
def openQuote = if ctx.mode.is(Mode.QuotedExprPattern) then "'{" else "'["
168+
def closeQuote = if ctx.mode.is(Mode.QuotedExprPattern) then "}" else "]"
169+
170+
def warnOnUnusedBounds(typeSym: Symbol) =
168171
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
169-
val (openQuote, closeQuote) = if ctx.mode.is(Mode.QuotedExprPattern) then ("'{", "}") else ("'[", "]")
170172
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos)
171173

174+
def warnOnInferredBounds(typeSym: Symbol) =
175+
if !(typeSymInfo =:= TypeBounds.empty) then
176+
if ctx.mode.is(Mode.QuotedExprPattern) then // TODO drop this guard once SIP-53 is non-experimental
177+
// TODO state in the message when this will no longer work.
178+
// - We could stabilize SIP-53 and start deprecation warnings for type quoted patterns in 3.4.0.
179+
// - Then we could drop type variable inference 3.5.0.
180+
report.deprecationWarning(em"Type variable `$tree` has partially inferred bounds$typeSymInfo.\n\nConsider defining bounds explicitly:\n $openQuote $typeSym$typeSymInfo; ... $closeQuote", tree.srcPos)
181+
172182
getQuotedPatternTypeVariable(tree.name.asTypeName) match
173183
case Some(typeSym) =>
174184
checkExperimentalFeature(
175185
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
176186
tree.srcPos,
177187
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
178-
warnOnInferredBounds(typeSym)
188+
warnOnUnusedBounds(typeSym)
179189
ref(typeSym)
180190
case None =>
181191
val spliceContext = quotePatternSpliceContext
182192
val name = tree.name.toTypeName
183193
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
184194
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
185195
val typeSym = newSymbol(spliceContext.owner, name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
196+
warnOnInferredBounds(typeSym)
186197
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
187198
addQuotedPatternTypeVariable(typeSym)
188199
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(using spliceContext)
@@ -536,8 +547,12 @@ trait QuotesAndSplices {
536547
object QuotesAndSplices {
537548
import tpd._
538549

550+
private enum QuotePattenKind:
551+
case Expr, Type
552+
539553
/** Key for mapping from quoted pattern type variable names into their symbol */
540554
private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]]
555+
private val QuotePattenKindKey = new Property.Key[QuotePattenKind]
541556

542557
/** Get the symbol for the quoted pattern type variable if it exists */
543558
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:13:18 ----------------------------------
2+
13 | 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:14:18 ----------------------------------
9+
14 | 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:17:18 ----------------------------------
16+
17 | 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:19:18 ----------------------------------
23+
19 | 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:19:21 ----------------------------------
30+
19 | 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:20:36 ----------------------------------
37+
20 | 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:26:17 ----------------------------------
44+
26 | case '[ F[t, t] ] => // error // will have a second error with SIP-53
45+
| ^
46+
| Ignored bound <: String
47+
|
48+
| Consider defining bounds explicitly:
49+
| '[ type t <: Int & String; ... ]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// scalac: -deprecation
2+
3+
import scala.quoted.*
4+
5+
class C[T <: Int]
6+
class D[T >: Null <: String]
7+
class E[T <: Int, U <: Int]
8+
class F[T <: Int, U <: String]
9+
10+
def test[T: Type](e: Expr[Any])(using Quotes) =
11+
e match
12+
case '{ $x: t } =>
13+
case '{ $x: C[t] } => // error
14+
case '{ $x: D[t] } => // error
15+
case '{ type t <: Int; $x: C[t] } =>
16+
17+
case '{ $x: E[t, t] } => // error
18+
19+
case '{ $x: F[t, t] } => // error // error
20+
case '{ type t <: Int; $x: F[t, t] } => // error
21+
22+
Type.of[T] match
23+
case '[ C[t] ] => // will have an error with SIP-53
24+
case '[ D[t] ] => // will have an error with SIP-53
25+
case '[ E[t, t] ] => // will have an error with SIP-53
26+
case '[ F[t, t] ] => // error // will have a second error with SIP-53

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
-- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:22 ---------------------------------------------
2-
5 | case '{ $_ : F[t, t]; () } => // warn // error
1+
-- Deprecation Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:19 ---------------------------------
2+
7 | case '{ $_ : F[t, t]; () } => // warn // error
3+
| ^
4+
| Type variable `t` has partially inferred bounds <: Int.
5+
|
6+
| Consider defining bounds explicitly:
7+
| '{ type t <: Int; ... }
8+
-- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:22 ---------------------------------------------
9+
7 | case '{ $_ : F[t, t]; () } => // warn // error
310
| ^
411
| Ignored bound <: Double
512
|
613
| Consider defining bounds explicitly:
714
| '{ 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
15+
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:12 --------------------------
16+
7 | case '{ $_ : F[t, t]; () } => // warn // error
1017
| ^
1118
| Type argument t does not conform to upper bound Double in inferred type F[t, t]
1219
|

tests/neg-macros/quote-type-variable-no-inference-2.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// scalac: -deprecation
2+
13
import scala.quoted.*
24

35
def test2(x: Expr[Any])(using Quotes) =

0 commit comments

Comments
 (0)