diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 04509124bd7c..24c39e36c207 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -164,18 +164,28 @@ trait QuotesAndSplices { case pt: TypeBounds => pt case _ => TypeBounds.empty - def warnOnInferredBounds(typeSym: Symbol) = + def openQuote = if ctx.mode.is(Mode.QuotedExprPattern) then "'{" else "'[" + def closeQuote = if ctx.mode.is(Mode.QuotedExprPattern) then "}" else "]" + + def warnOnUnusedBounds(typeSym: Symbol) = if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then - val (openQuote, closeQuote) = if ctx.mode.is(Mode.QuotedExprPattern) then ("'{", "}") else ("'[", "]") report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly:\n $openQuote $typeSym${typeSym.info & typeSymInfo}; ... $closeQuote", tree.srcPos) + def warnOnInferredBounds(typeSym: Symbol) = + if !(typeSymInfo =:= TypeBounds.empty) then + if ctx.mode.is(Mode.QuotedExprPattern) then // TODO drop this guard once SIP-53 is non-experimental + // TODO state in the message when this will no longer work. + // - We could stabilize SIP-53 and start deprecation warnings for type quoted patterns in 3.4.0. + // - Then we could drop type variable inference 3.5.0. + report.deprecationWarning(em"Type variable `$tree` has partially inferred bounds$typeSymInfo.\n\nConsider defining bounds explicitly:\n $openQuote $typeSym$typeSymInfo; ... $closeQuote", tree.srcPos) + getQuotedPatternTypeVariable(tree.name.asTypeName) match case Some(typeSym) => checkExperimentalFeature( "support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)", tree.srcPos, "\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html") - warnOnInferredBounds(typeSym) + warnOnUnusedBounds(typeSym) ref(typeSym) case None => val spliceContext = quotePatternSpliceContext @@ -183,6 +193,7 @@ trait QuotesAndSplices { val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName) val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven) val typeSym = newSymbol(spliceContext.owner, name, EmptyFlags, typeSymInfo, NoSymbol, tree.span) + warnOnInferredBounds(typeSym) typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span))) addQuotedPatternTypeVariable(typeSym) val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(using spliceContext) @@ -536,8 +547,12 @@ trait QuotesAndSplices { object QuotesAndSplices { import tpd._ + private enum QuotePattenKind: + case Expr, Type + /** Key for mapping from quoted pattern type variable names into their symbol */ private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]] + private val QuotePattenKindKey = new Property.Key[QuotePattenKind] /** Get the symbol for the quoted pattern type variable if it exists */ def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] = diff --git a/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.check b/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.check new file mode 100644 index 000000000000..ef1ade8da645 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.check @@ -0,0 +1,49 @@ +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:13:18 ---------------------------------- +13 | case '{ $x: C[t] } => // error + | ^ + | Type variable `t` has partially inferred bounds <: Int. + | + | Consider defining bounds explicitly: + | '{ type t <: Int; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:14:18 ---------------------------------- +14 | case '{ $x: D[t] } => // error + | ^ + | Type variable `t` has partially inferred bounds >: Null <: String. + | + | Consider defining bounds explicitly: + | '{ type t >: Null <: String; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:17:18 ---------------------------------- +17 | case '{ $x: E[t, t] } => // error + | ^ + | Type variable `t` has partially inferred bounds <: Int. + | + | Consider defining bounds explicitly: + | '{ type t <: Int; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:19:18 ---------------------------------- +19 | case '{ $x: F[t, t] } => // error // error + | ^ + | Type variable `t` has partially inferred bounds <: Int. + | + | Consider defining bounds explicitly: + | '{ type t <: Int; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:19:21 ---------------------------------- +19 | case '{ $x: F[t, t] } => // error // error + | ^ + | Ignored bound <: String + | + | Consider defining bounds explicitly: + | '{ type t <: Int & String; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:20:36 ---------------------------------- +20 | case '{ type t <: Int; $x: F[t, t] } => // error + | ^ + | Ignored bound <: String + | + | Consider defining bounds explicitly: + | '{ type t <: Int & String; ... } +-- Error: tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala:26:17 ---------------------------------- +26 | case '[ F[t, t] ] => // error // will have a second error with SIP-53 + | ^ + | Ignored bound <: String + | + | Consider defining bounds explicitly: + | '[ type t <: Int & String; ... ] diff --git a/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala b/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala new file mode 100644 index 000000000000..a88cc53ee3bf --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/quote-type-var-with-bounds.scala @@ -0,0 +1,26 @@ +// scalac: -deprecation + +import scala.quoted.* + +class C[T <: Int] +class D[T >: Null <: String] +class E[T <: Int, U <: Int] +class F[T <: Int, U <: String] + +def test[T: Type](e: Expr[Any])(using Quotes) = + e match + case '{ $x: t } => + case '{ $x: C[t] } => // error + case '{ $x: D[t] } => // error + case '{ type t <: Int; $x: C[t] } => + + case '{ $x: E[t, t] } => // error + + case '{ $x: F[t, t] } => // error // error + case '{ type t <: Int; $x: F[t, t] } => // error + + Type.of[T] match + case '[ C[t] ] => // will have an error with SIP-53 + case '[ D[t] ] => // will have an error with SIP-53 + case '[ E[t, t] ] => // will have an error with SIP-53 + case '[ F[t, t] ] => // error // will have a second error with SIP-53 diff --git a/tests/neg-macros/quote-type-variable-no-inference-2.check b/tests/neg-macros/quote-type-variable-no-inference-2.check index 2099e12a1a6e..2328d5f8de7d 100644 --- a/tests/neg-macros/quote-type-variable-no-inference-2.check +++ b/tests/neg-macros/quote-type-variable-no-inference-2.check @@ -1,12 +1,19 @@ --- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:22 --------------------------------------------- -5 | case '{ $_ : F[t, t]; () } => // warn // error +-- Deprecation Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:19 --------------------------------- +7 | case '{ $_ : F[t, t]; () } => // warn // error + | ^ + | Type variable `t` has partially inferred bounds <: Int. + | + | Consider defining bounds explicitly: + | '{ type t <: Int; ... } +-- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:22 --------------------------------------------- +7 | case '{ $_ : F[t, t]; () } => // warn // error | ^ | Ignored bound <: Double | | Consider defining bounds explicitly: | '{ type t <: Int & Double; ... } --- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:12 -------------------------- -5 | case '{ $_ : F[t, t]; () } => // warn // error +-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-2.scala:7:12 -------------------------- +7 | case '{ $_ : F[t, t]; () } => // warn // error | ^ | Type argument t does not conform to upper bound Double in inferred type F[t, t] | diff --git a/tests/neg-macros/quote-type-variable-no-inference-2.scala b/tests/neg-macros/quote-type-variable-no-inference-2.scala index 1cb0d3dab7b3..6f7b428cf8a6 100644 --- a/tests/neg-macros/quote-type-variable-no-inference-2.scala +++ b/tests/neg-macros/quote-type-variable-no-inference-2.scala @@ -1,3 +1,5 @@ +// scalac: -deprecation + import scala.quoted.* def test2(x: Expr[Any])(using Quotes) =