diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e0791f313970..819b32a6733c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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) @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 139c4a3fb52b..86811bfae1be 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -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(", ",", ")") @@ -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. @@ -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") diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index de611db6c14e..8443f5baa867 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index e882d4884226..e7ecfd1d5255 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -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) @@ -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 => @@ -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) @@ -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 @@ -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 = @@ -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) @@ -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(quoteOwner(ctx)) + + /** Owner of the quoted pattern */ + private def quoteOwner(ctx: Context): Symbol = + if ctx.mode.isQuotedPattern then quoteOwner(ctx.outer) else ctx.owner + } diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index a1f599847114..a31b45c5f4c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -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) @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index dcd3a9f17f6a..0b357c5c00d9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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) @@ -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) @@ -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) @@ -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) } diff --git a/tests/neg-macros/quote-type-variable-no-inference.check b/tests/neg-macros/quote-type-variable-no-inference.check index 0f2f816fcac3..ee00ddde191b 100644 --- a/tests/neg-macros/quote-type-variable-no-inference.check +++ b/tests/neg-macros/quote-type-variable-no-inference.check @@ -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 | ^