Skip to content

Commit

Permalink
Fix ignored type variable bound warning in type quote pattern (#18199)
Browse files Browse the repository at this point in the history
See the diff in tests/neg-macros/quote-type-variable-no-inference.check
  • Loading branch information
nicolasstucki authored Jul 20, 2023
2 parents 78e7163 + c8a7eee commit dde69ce
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 30 deletions.
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 @@ -1014,7 +1014,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 @@ -1363,7 +1363,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 @@ -709,7 +709,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 @@ -121,7 +121,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 @@ -132,7 +134,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
8 changes: 4 additions & 4 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 @@ -966,7 +966,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 @@ -2221,7 +2221,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 @@ -4268,7 +4268,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; ... }
-- [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

0 comments on commit dde69ce

Please sign in to comment.