From c3de0caa0a8ddf7bcdc31649def550eb4c6d5558 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:17:05 -0800 Subject: [PATCH 1/4] Dialect: add `allowQuotedTypeVariables` --- .../shared/src/main/scala/scala/meta/Dialect.scala | 12 ++++++++++++ .../src/main/scala/scala/meta/dialects/package.scala | 1 + .../meta/tests/prettyprinters/PublicSuite.scala | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala b/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala index 854c6fcd6f..a439938212 100644 --- a/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala +++ b/scalameta/dialects/shared/src/main/scala/scala/meta/Dialect.scala @@ -110,6 +110,8 @@ final class Dialect private[meta] ( val allowInfixMods: Boolean, // Scala 3 splices/quotes val allowSpliceAndQuote: Boolean, + // https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html + val allowQuotedTypeVariables: Boolean, // Scala 3 disallowed symbol literals val allowSymbolLiterals: Boolean, // Scala 3 disallowed symbol literals @@ -235,6 +237,7 @@ final class Dialect private[meta] ( allowTypeMatch = false, allowInfixMods = false, allowSpliceAndQuote = false, + allowQuotedTypeVariables = false, allowSymbolLiterals = true, allowDependentFunctionTypes = false, allowPostfixStarVarargSplices = false, @@ -409,6 +412,9 @@ final class Dialect private[meta] ( def withAllowSpliceAndQuote(newValue: Boolean): Dialect = { privateCopy(allowSpliceAndQuote = newValue) } + def withAllowQuotedTypeVariables(newValue: Boolean): Dialect = { + privateCopy(allowQuotedTypeVariables = newValue) + } def withAllowSymbolLiterals(newValue: Boolean): Dialect = { privateCopy(allowSymbolLiterals = newValue) } @@ -524,6 +530,7 @@ final class Dialect private[meta] ( allowTypeMatch: Boolean = this.allowTypeMatch, allowInfixMods: Boolean = this.allowInfixMods, allowSpliceAndQuote: Boolean = this.allowSpliceAndQuote, + allowQuotedTypeVariables: Boolean = this.allowQuotedTypeVariables, allowSymbolLiterals: Boolean = this.allowSymbolLiterals, allowDependentFunctionTypes: Boolean = this.allowDependentFunctionTypes, allowPostfixStarVarargSplices: Boolean = this.allowPostfixStarVarargSplices, @@ -585,6 +592,7 @@ final class Dialect private[meta] ( allowTypeMatch = allowTypeMatch, allowInfixMods = allowInfixMods, allowSpliceAndQuote = allowSpliceAndQuote, + allowQuotedTypeVariables = allowQuotedTypeVariables, allowSymbolLiterals = allowSymbolLiterals, allowDependentFunctionTypes = allowDependentFunctionTypes, allowPostfixStarVarargSplices = allowPostfixStarVarargSplices, @@ -648,6 +656,7 @@ final class Dialect private[meta] ( allowTypeMatch = allowTypeMatch, allowInfixMods = allowInfixMods, allowSpliceAndQuote = allowSpliceAndQuote, + allowQuotedTypeVariables = allowQuotedTypeVariables, allowSymbolLiterals = allowSymbolLiterals, allowDependentFunctionTypes = allowDependentFunctionTypes, allowPostfixStarVarargSplices = allowPostfixStarVarargSplices, @@ -736,6 +745,7 @@ final class Dialect private[meta] ( allowTypeMatch: Boolean, allowInfixMods: Boolean, allowSpliceAndQuote: Boolean, + allowQuotedTypeVariables: Boolean, allowSymbolLiterals: Boolean, allowDependentFunctionTypes: Boolean, allowPostfixStarVarargSplices: Boolean, @@ -795,6 +805,7 @@ final class Dialect private[meta] ( && this.allowTypeMatch == allowTypeMatch && this.allowInfixMods == allowInfixMods && this.allowSpliceAndQuote == allowSpliceAndQuote + && this.allowQuotedTypeVariables == allowQuotedTypeVariables && this.allowSymbolLiterals == allowSymbolLiterals && this.allowDependentFunctionTypes == allowDependentFunctionTypes && this.allowPostfixStarVarargSplices == allowPostfixStarVarargSplices @@ -857,6 +868,7 @@ final class Dialect private[meta] ( allowTypeMatch = that.allowTypeMatch, allowInfixMods = that.allowInfixMods, allowSpliceAndQuote = that.allowSpliceAndQuote, + allowQuotedTypeVariables = that.allowQuotedTypeVariables, allowSymbolLiterals = that.allowSymbolLiterals, allowDependentFunctionTypes = that.allowDependentFunctionTypes, allowPostfixStarVarargSplices = that.allowPostfixStarVarargSplices, diff --git a/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala b/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala index 6418b7bc98..b9c3025962 100644 --- a/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala +++ b/scalameta/dialects/shared/src/main/scala/scala/meta/dialects/package.scala @@ -153,6 +153,7 @@ package object dialects { .withAllowParamClauseInterleaving(true) implicit val Scala34: Dialect = Scala33 + .withAllowQuotedTypeVariables(true) implicit val Scala3: Dialect = Scala34 diff --git a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/PublicSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/PublicSuite.scala index c8587f4239..77a41aa060 100644 --- a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/PublicSuite.scala +++ b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/PublicSuite.scala @@ -109,7 +109,7 @@ class PublicSuite extends TreeSuiteBase { } test("scala.meta.dialects.Scala33.toString") { - assertNoDiff(scala.meta.dialects.Scala33.toString, "Scala34") + assertNoDiff(scala.meta.dialects.Scala33.toString, "Scala33") } test("scala.meta.dialects.Scala34.toString") { From a5ffaa253eee88066d1366af8902ab11dc7a61e5 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Wed, 6 Mar 2024 07:58:59 -0800 Subject: [PATCH 2/4] MacroSuite: add test with quoted type blocks --- .../meta/tests/parsers/dotty/MacroSuite.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala index eaeef3c3f5..58ca540d6e 100644 --- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala +++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala @@ -440,4 +440,28 @@ class MacroSuite extends BaseDottySuite { ) ) } + + test("#3610 1") { + val code = "'[ List[Int] ]" + val tree = Term.QuotedMacroType(Type.Apply(pname("List"), List(pname("Int")))) + runTestAssert[Stat](code)(tree) + } + + test("#3610 2") { + val code = "'[ type t = Int; List[t] ]" + val error = + """|:1: error: identifier expected but type found + |'[ type t = Int; List[t] ] + | ^""".stripMargin + runTestError[Stat](code, error) + } + + test("#3610 3") { + val code = "'[ type tail <: Tuple; *:[Int, tail] ]" + val error = + """|:1: error: identifier expected but type found + |'[ type tail <: Tuple; *:[Int, tail] ] + | ^""".stripMargin + runTestError[Stat](code, error) + } } From ef07d6312f29036a3e0d90c060a543fc6b60275e Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:18:28 -0800 Subject: [PATCH 3/4] Trees: add `Type.Block`, containing TypeDefs To support SIP-53, it's been defined as `TypeBlock` under https://dotty.epfl.ch/docs/internals/syntax.html. --- .../shared/src/main/scala/scala/meta/Trees.scala | 3 +++ .../meta/internal/prettyprinters/TreeSyntax.scala | 1 + .../scala/meta/tests/api/SurfaceSuite.scala | 1 + .../scala/meta/tests/trees/ReflectionSuite.scala | 3 ++- .../tests/prettyprinters/SyntacticSuite.scala | 15 +++++++++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala b/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala index bd9e1013fc..42c836f1bd 100644 --- a/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala +++ b/scalameta/trees/shared/src/main/scala/scala/meta/Trees.scala @@ -424,6 +424,9 @@ object Type { @ast class ParamClause(values: List[Param]) extends Member.ParamClause @ast class Match(tpe: Type, cases: List[TypeCase] @nonEmpty) extends Type with Tree.WithCases + + @ast class Block(typeDefs: List[Stat.TypeDef], tpe: Type) extends Type + def fresh(): Type.Name = fresh("fresh") def fresh(prefix: String): Type.Name = Type.Name(prefix + Fresh.nextId()) } diff --git a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala index 0cd1399926..a533237411 100644 --- a/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala +++ b/scalameta/trees/shared/src/main/scala/scala/meta/internal/prettyprinters/TreeSyntax.scala @@ -705,6 +705,7 @@ object TreeSyntax { } val cbounds = r(t.cbounds.map { s(kw(":"), " ", _) }) s(w(mods, " "), variance, t.name, t.tparamClause, tbounds, vbounds, cbounds) + case t: Type.Block => s(w(r(t.typeDefs, "; "), "; "), t.tpe) // Pat case t: Pat.Var => diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/api/SurfaceSuite.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/api/SurfaceSuite.scala index 6777f437c8..f70143c101 100644 --- a/tests/jvm/src/test/scala-2.13/scala/meta/tests/api/SurfaceSuite.scala +++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/api/SurfaceSuite.scala @@ -469,6 +469,7 @@ class SurfaceSuite extends FunSuite { |scala.meta.Type.Apply |scala.meta.Type.ApplyInfix |scala.meta.Type.ArgClause + |scala.meta.Type.Block |scala.meta.Type.Bounds |scala.meta.Type.ByName |scala.meta.Type.ContextFunction diff --git a/tests/jvm/src/test/scala-2.13/scala/meta/tests/trees/ReflectionSuite.scala b/tests/jvm/src/test/scala-2.13/scala/meta/tests/trees/ReflectionSuite.scala index 717e41595b..c20297fa2e 100644 --- a/tests/jvm/src/test/scala-2.13/scala/meta/tests/trees/ReflectionSuite.scala +++ b/tests/jvm/src/test/scala-2.13/scala/meta/tests/trees/ReflectionSuite.scala @@ -24,7 +24,7 @@ class ReflectionSuite extends FunSuite { val sym = symbolOf[scala.meta.Tree] assert(sym.isRoot) val root = sym.asRoot - assertEquals((root.allBranches.length, root.allLeafs.length), (50, 408)) + assertEquals((root.allBranches.length, root.allLeafs.length), (50, 410)) } test("If") { @@ -98,6 +98,7 @@ class ReflectionSuite extends FunSuite { |List[scala.meta.Mod] |List[scala.meta.Pat] |List[scala.meta.Source] + |List[scala.meta.Stat.TypeDef] |List[scala.meta.Stat] |List[scala.meta.Term.Name] |List[scala.meta.Term.Param] diff --git a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala index 13e434ce9e..7107f6ce56 100644 --- a/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala +++ b/tests/shared/src/test/scala/scala/meta/tests/prettyprinters/SyntacticSuite.scala @@ -2064,4 +2064,19 @@ class SyntacticSuite extends scala.meta.tests.parsers.ParseSuite { ) } + test("#3610 Type.Block 1") { + val tree = Type.Block(Nil, pname("Int")) + assertEquals(tree.syntax, "Int") + } + + test("#3610 Type.Block 2") { + val tree = Type.Block(List(Decl.Type(Nil, pname("t"), Nil, noBounds)), pname("t")) + assertEquals(tree.syntax, "type t; t") + } + + test("#3610 Type.Block 3") { + val tree = Type.Block(List(Defn.Type(Nil, pname("t"), Nil, pname("Int"))), pname("t")) + assertEquals(tree.syntax, "type t = Int; t") + } + } From 3e44535f2c2ac5db8778cee53d0f8d9db0959198 Mon Sep 17 00:00:00 2001 From: Albert Meltzer <7529386+kitbellew@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:59:38 -0800 Subject: [PATCH 4/4] ScalametaParser: parse quoted type blocks --- .../internal/parsers/ScalametaParser.scala | 20 +++++++++++++++- .../meta/tests/parsers/dotty/MacroSuite.scala | 24 +++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala index d9ed4b115d..81fee45e10 100644 --- a/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala +++ b/scalameta/parsers/shared/src/main/scala/scala/meta/internal/parsers/ScalametaParser.scala @@ -849,6 +849,23 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser => }).getOrElse(t) } + def typeBlock(): Type = + // TypeBlock, https://dotty.epfl.ch/docs/internals/syntax.html#expressions-3 + if (dialect.allowQuotedTypeVariables && token.is[KwType]) autoPos { + val typeDefs = listBy[Stat.TypeDef] { buf => + @tailrec def iter(): Unit = { + buf += typeDefOrDcl(Nil) + if (StatSep(token)) { + next() + if (token.is[KwType]) iter() + } + } + iter() + } + Type.Block(typeDefs, typ()) + } + else typ() + private def typeFuncOnArrow( paramPos: Int, params: List[Type], @@ -2297,7 +2314,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser => next() token match { case _: LeftBrace => Term.QuotedMacroExpr(autoPos(inBracesOnOpen(blockWithinDelims()))) - case _: LeftBracket => Term.QuotedMacroType(inBracketsOnOpen(typ())) + case _: LeftBracket => Term.QuotedMacroType(inBracketsOnOpen(typeBlock())) case t => syntaxError("Quotation only works for expressions and types", at = t) } }) @@ -2982,6 +2999,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser => * initiated from non-pattern context. */ def typ() = outPattern.typ() + private def typeBlock() = outPattern.typeBlock() def typeIndentedOpt() = outPattern.typeIndentedOpt() def quasiquoteType() = outPattern.quasiquoteType() def entrypointType() = outPattern.entrypointType() diff --git a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala index 58ca540d6e..a9eddb30db 100644 --- a/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala +++ b/tests/shared/src/test/scala/scala/meta/tests/parsers/dotty/MacroSuite.scala @@ -449,19 +449,23 @@ class MacroSuite extends BaseDottySuite { test("#3610 2") { val code = "'[ type t = Int; List[t] ]" - val error = - """|:1: error: identifier expected but type found - |'[ type t = Int; List[t] ] - | ^""".stripMargin - runTestError[Stat](code, error) + val tree = Term.QuotedMacroType( + Type.Block( + List(Defn.Type(Nil, pname("t"), Nil, pname("Int"), noBounds)), + Type.Apply(pname("List"), List(pname("t"))) + ) + ) + runTestAssert[Stat](code)(tree) } test("#3610 3") { val code = "'[ type tail <: Tuple; *:[Int, tail] ]" - val error = - """|:1: error: identifier expected but type found - |'[ type tail <: Tuple; *:[Int, tail] ] - | ^""".stripMargin - runTestError[Stat](code, error) + val tree = Term.QuotedMacroType( + Type.Block( + List(Decl.Type(Nil, pname("tail"), Nil, bounds(hi = "Tuple"))), + Type.Apply(pname("*:"), List(pname("Int"), pname("tail"))) + ) + ) + runTestAssert[Stat](code)(tree) } }