Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ignored type variable bound warning in type quote pattern #18199

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ object desugar {
* if the type has a pattern variable name
*/
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
assert(ctx.mode.is(Mode.QuotedPattern))
assert(ctx.mode.isQuotedPattern)
if tree.name.isVarPattern && !tree.isBackquoted then
val patternTypeAnnot = New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
Expand Down Expand Up @@ -1359,7 +1359,7 @@ object desugar {
case tree: ValDef => valDef(tree)
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree)
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
else tree
case tree: DefDef =>
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
Expand Down
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ case class Mode(val bits: Int) extends AnyVal {

def isExpr: Boolean = (this & PatternOrTypeBits) == None

/** Are we in the body of quoted pattern? */
def isQuotedPattern: Boolean = (this & QuotedPatternBits) != None

override def toString: String =
(0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")")

Expand Down Expand Up @@ -69,6 +72,9 @@ object Mode {
*/
val Printing: Mode = newMode(10, "Printing")

/** Are we in a quote the body of quoted type pattern? */
val QuotedTypePattern: Mode = newMode(11, "QuotedTypePattern")

/** We are currently in a `viewExists` check. In that case, ambiguous
* implicits checks are disabled and we succeed with the first implicit
* found.
Expand Down Expand Up @@ -128,8 +134,10 @@ object Mode {
/** Are we trying to find a hidden implicit? */
val FindHiddenImplicits: Mode = newMode(24, "FindHiddenImplicits")

/** Are we in a quote in a pattern? */
val QuotedPattern: Mode = newMode(25, "QuotedPattern")
/** Are we in a quote the body of quoted expression pattern? */
val QuotedExprPattern: Mode = newMode(25, "QuotedExprPattern")

val QuotedPatternBits: Mode = QuotedExprPattern | QuotedTypePattern

/** Are we typechecking the rhs of an extension method? */
val InExtensionMethod: Mode = newMode(26, "InExtensionMethod")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ object TreeChecker {
super.typedQuotePattern(tree, pt)

override def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree =
assert(ctx.mode.is(Mode.QuotedPattern))
assert(ctx.mode.isQuotedPattern)
def isAppliedIdent(rhs: untpd.Tree): Boolean = rhs match
case _: Ident => true
case rhs: GenericApply => isAppliedIdent(rhs.fun)
Expand Down
49 changes: 31 additions & 18 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ trait QuotesAndSplices {
def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = {
record("typedSplice")
checkSpliceOutsideQuote(tree)
assert(!ctx.mode.is(Mode.QuotedPattern))
assert(!ctx.mode.isQuotedPattern)
tree.expr match {
case untpd.Quote(innerExpr, Nil) if innerExpr.isTerm =>
report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos)
Expand Down Expand Up @@ -110,8 +110,6 @@ trait QuotesAndSplices {
def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = {
record("typedSplicePattern")
if isFullyDefined(pt, ForceDegree.flipBottom) then
def patternOuterContext(ctx: Context): Context =
if (ctx.mode.is(Mode.QuotedPattern)) patternOuterContext(ctx.outer) else ctx
val typedArgs = withMode(Mode.InQuotePatternHoasArgs) {
tree.args.map {
case arg: untpd.Ident =>
Expand All @@ -125,8 +123,7 @@ trait QuotesAndSplices {
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(patternOuterContext(ctx).owner))
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(using quotePatternSpliceContext)
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
Expand All @@ -145,7 +142,7 @@ trait QuotesAndSplices {
* The prototype must be fully defined to be able to infer the type of `R`.
*/
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
assert(ctx.mode.is(Mode.QuotedPattern))
assert(ctx.mode.isQuotedPattern)
val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked
def isInBraces: Boolean = splice.span.end != splice.body.span.end
if isInBraces then // ${x}(...) match an application
Expand All @@ -166,26 +163,29 @@ trait QuotesAndSplices {
val typeSymInfo = pt match
case pt: TypeBounds => pt
case _ => TypeBounds.empty

def warnOnInferredBounds(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)

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")
if !(typeSymInfo =:= TypeBounds.empty) && !(typeSym.info <:< typeSymInfo) then
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym${typeSym.info & typeSymInfo}; ... }`", tree.srcPos)
warnOnInferredBounds(typeSym)
ref(typeSym)
case None =>
def spliceOwner(ctx: Context): Symbol =
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
val spliceContext = quotePatternSpliceContext
val name = tree.name.toTypeName
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
val typeSym = newSymbol(spliceContext.owner, name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
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.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(using spliceContext)
pat.select(tpnme.Underlying)

private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =
Expand Down Expand Up @@ -454,7 +454,7 @@ trait QuotesAndSplices {
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")

val (typeTypeVariables, patternCtx) =
val quoteCtx = quotePatternContext()
val quoteCtx = quotePatternContext(quoted.isType)
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
else typedBlockStats(untpdTypeVariables)(using quoteCtx)

Expand Down Expand Up @@ -543,13 +543,26 @@ object QuotesAndSplices {
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
ctx.property(TypeVariableKey).get.get(name)

/** Get the symbol for the quoted pattern type variable if it exists */
/** Get the symbol for the quoted pattern type variable if it exists */
def addQuotedPatternTypeVariable(sym: Symbol)(using Context): Unit =
ctx.property(TypeVariableKey).get.update(sym.name.asTypeName, sym)

/** Context used to type the contents of a quoted */
def quotePatternContext()(using Context): Context =
/** Context used to type the contents of a quote pattern */
def quotePatternContext(isTypePattern: Boolean)(using Context): Context =
quoteContext.fresh.setNewScope
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
.addMode(if isTypePattern then Mode.QuotedTypePattern else Mode.QuotedExprPattern)
.retractMode(Mode.Pattern) // TODO do we need Mode.QuotedPattern?
.setProperty(TypeVariableKey, collection.mutable.Map.empty)

/** Context used to type the contents of a quote pattern splice */
def quotePatternSpliceContext(using Context): Context =
spliceContext
.retractMode(Mode.QuotedPatternBits)
.addMode(Mode.Pattern)
.withOwner(quotePatternOwner(ctx))

/** First outer context owner that is outside of a quoted pattern context. */
private def quotePatternOwner(ctx: Context): Symbol =
if ctx.mode.isQuotedPattern then quotePatternOwner(ctx.outer) else ctx.owner

}
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
override def typedQuotePattern(tree: untpd.QuotePattern, pt: Type)(using Context): Tree =
assertTyped(tree)
val bindings1 = tree.bindings.map(typed(_))
val bodyCtx = quoteContext.retractMode(Mode.Pattern).addMode(Mode.QuotedPattern)
val bodyCtx = quoteContext
.retractMode(Mode.Pattern)
.addMode(if tree.body.isType then Mode.QuotedTypePattern else Mode.QuotedExprPattern)
val body1 = typed(tree.body, promote(tree).bodyType)(using bodyCtx)
val quotes1 = typed(tree.quotes, defn.QuotesClass.typeRef)
untpd.cpy.QuotePattern(tree)(bindings1, body1, quotes1).withType(tree.typeOpt)
Expand All @@ -125,7 +127,7 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking
val patternTpe =
if args1.isEmpty then tree.typeOpt
else defn.FunctionType(args1.size).appliedTo(args1.map(_.tpe) :+ tree.typeOpt)
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPattern)
val bodyCtx = spliceContext.addMode(Mode.Pattern).retractMode(Mode.QuotedPatternBits)
val body1 = typed(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patternTpe))(using bodyCtx)
val args = tree.args.mapconserve(typedExpr(_))
untpd.cpy.SplicePattern(tree)(body1, args1).withType(tree.typeOpt)
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
return tree.withType(defn.AnyType)
if untpd.isVarPattern(tree) && name.isTermName then
return typed(desugar.patternVar(tree), pt)
else if ctx.mode.is(Mode.QuotedPattern) then
else if ctx.mode.isQuotedPattern then
if untpd.isVarPattern(tree) && name.isTypeName then
return typedQuotedTypeVar(tree, pt)
end if
Expand Down Expand Up @@ -964,7 +964,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// so the expected type is the union `Seq[T] | Array[_ <: T]`.
val ptArg =
// FIXME(#8680): Quoted patterns do not support Array repeated arguments
if ctx.mode.is(Mode.QuotedPattern) then
if ctx.mode.isQuotedPattern then
pt.translateFromRepeated(toArray = false, translateWildcard = true)
else
pt.translateFromRepeated(toArray = false, translateWildcard = true)
Expand Down Expand Up @@ -2225,7 +2225,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val (desugaredArg, argPt) =
if ctx.mode.is(Mode.Pattern) then
(if (untpd.isVarPattern(arg)) desugar.patternVar(arg) else arg, tparamBounds)
else if ctx.mode.is(Mode.QuotedPattern) then
else if ctx.mode.isQuotedPattern then
(arg, tparamBounds)
else
(arg, WildcardType)
Expand Down Expand Up @@ -3957,7 +3957,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
sym.isConstructor
|| sym.matchNullaryLoosely
|| Feature.warnOnMigration(msg, tree.srcPos, version = `3.0`)
&& {
&& {
msg.actions
.headOption
.foreach(Rewrites.applyAction)
Expand Down Expand Up @@ -4274,7 +4274,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
tree.tpe.EtaExpand(tp.typeParamSymbols)
tree.withType(tp1)
}
if (ctx.mode.is(Mode.Pattern) || ctx.mode.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1
if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1
else err.typeMismatch(tree1, pt)
}

Expand Down
13 changes: 13 additions & 0 deletions tests/neg-macros/quote-type-variable-no-inference-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Warning: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:22 ---------------------------------------------
5 | case '{ $_ : F[t, t]; () } => // warn // error
| ^
| Ignored bound <: Double
|
| Consider defining bounds explicitly:
| '{ type t <: Int & Double; ... }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could take advantage of #17561 to automatically propose a rewrite that does that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am considering doing that. The complication is finding the position where this needs to be inserted. It would be also be a bit more challenging if we consider handling two of these errors at the same time. I plan to try this in the near future.

-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-2.scala:5:12 --------------------------
5 | case '{ $_ : F[t, t]; () } => // warn // error
| ^
| Type argument t does not conform to upper bound Double in inferred type F[t, t]
|
| longer explanation available when compiling with `-explain`
8 changes: 8 additions & 0 deletions tests/neg-macros/quote-type-variable-no-inference-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted.*

def test2(x: Expr[Any])(using Quotes) =
x match
case '{ $_ : F[t, t]; () } => // warn // error
case '{ type u <: Int & Double; $_ : F[u, u] } =>

type F[X <: Int, Y <: Double]
20 changes: 20 additions & 0 deletions tests/neg-macros/quote-type-variable-no-inference-3.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:5:22 ---------------------------------------------
5 | case '{ $_ : F[t, t]; () } => // warn // error
| ^
| Ignored bound <: Comparable[U]
|
| Consider defining bounds explicitly:
| '{ type t <: Comparable[U]; ... }
-- Warning: tests/neg-macros/quote-type-variable-no-inference-3.scala:6:49 ---------------------------------------------
6 | case '{ type u <: Comparable[`u`]; $_ : F[u, u] } =>
| ^
| Ignored bound <: Comparable[Any]
|
| Consider defining bounds explicitly:
| '{ type u <: Comparable[u] & Comparable[Any]; ... }
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference-3.scala:5:12 --------------------------
5 | case '{ $_ : F[t, t]; () } => // warn // error
| ^
| Type argument t does not conform to upper bound Comparable[t] in inferred type F[t, t]
|
| longer explanation available when compiling with `-explain`
8 changes: 8 additions & 0 deletions tests/neg-macros/quote-type-variable-no-inference-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted.*

def test2(x: Expr[Any])(using Quotes) =
x match
case '{ $_ : F[t, t]; () } => // warn // error
case '{ type u <: Comparable[`u`]; $_ : F[u, u] } =>

type F[T, U <: Comparable[U]]
3 changes: 2 additions & 1 deletion tests/neg-macros/quote-type-variable-no-inference.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
| ^
| Ignored bound <: Double
|
| Consider defining bounds explicitly `'{ type t <: Int & Double; ... }`
| Consider defining bounds explicitly:
| '[ type t <: Int & Double; ... ]
-- [E057] Type Mismatch Error: tests/neg-macros/quote-type-variable-no-inference.scala:5:9 -----------------------------
5 | case '[ F[t, t] ] => // warn // error // error
| ^
Expand Down