Skip to content

Commit a3015f4

Browse files
authored
Add the possibility to create a typeSymbol in the Quotes API (#20347)
Closes #19448
2 parents 35c7d74 + 5d06f96 commit a3015f4

File tree

11 files changed

+216
-0
lines changed

11 files changed

+216
-0
lines changed

Diff for: compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+17
Original file line numberDiff line numberDiff line change
@@ -2649,6 +2649,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26492649
def newBind(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol =
26502650
checkValidFlags(flags.toTermFlags, Flags.validBindFlags)
26512651
dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Case, tpe)
2652+
2653+
def newTypeAlias(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol =
2654+
checkValidFlags(flags.toTypeFlags, Flags.validTypeAliasFlags)
2655+
assert(!tpe.isInstanceOf[Types.TypeBounds], "Passed `tpe` into newTypeAlias should not represent TypeBounds")
2656+
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags, dotc.core.Types.TypeAlias(tpe), privateWithin)
2657+
2658+
def newBoundedType(owner: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol =
2659+
checkValidFlags(flags.toTypeFlags, Flags.validBoundedTypeFlags)
2660+
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags | dotc.core.Flags.Deferred, tpe, privateWithin)
2661+
26522662
def noSymbol: Symbol = dotc.core.Symbols.NoSymbol
26532663

26542664
private inline def checkValidFlags(inline flags: Flags, inline valid: Flags): Unit =
@@ -2989,6 +2999,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
29892999

29903000
// Keep: aligned with Quotes's `newBind` doc
29913001
private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased
3002+
3003+
// Keep: aligned with Quotes's 'newBoundedType' doc
3004+
private[QuotesImpl] def validBoundedTypeFlags: Flags = Private | Protected | Override | Deferred | Final | Infix | Local
3005+
3006+
// Keep: aligned with Quotes's `newTypeAlias` doc
3007+
private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local
3008+
29923009
end Flags
29933010

29943011
given FlagsMethods: FlagsMethods with

Diff for: library/src/scala/quoted/Quotes.scala

+36
Original file line numberDiff line numberDiff line change
@@ -3963,6 +3963,42 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
39633963
// Keep: `flags` doc aligned with QuotesImpl's `validBindFlags`
39643964
def newBind(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol
39653965

3966+
/** Generate a new type symbol for a type alias with the given parent, name and type
3967+
*
3968+
* This symbol starts without an accompanying definition.
3969+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3970+
* this symbol to the TypeDef constructor.
3971+
*
3972+
* @param parent The owner of the type
3973+
* @param name The name of the type
3974+
* @param flags extra flags to with which symbol can be constructed. Can be `Private` | `Protected` | `Override` | `Final` | `Infix` | `Local`
3975+
* @param tpe The rhs the type alias
3976+
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol.
3977+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3978+
* direct or indirect children of the reflection context's owner.
3979+
*/
3980+
@experimental
3981+
// Keep: `flags` doc aligned with QuotesImpl's `validTypeAliasFlags`
3982+
def newTypeAlias(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol
3983+
3984+
/** Generate a new type symbol for a type bounds with the given parent, name and type
3985+
*
3986+
* This symbol starts without an accompanying definition.
3987+
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
3988+
* this symbol to the TypeDef constructor.
3989+
*
3990+
* @param parent The owner of the type
3991+
* @param name The name of the type
3992+
* @param flags extra flags to with which symbol can be constructed. `Deferred` flag will be added. Can be `Private` | `Protected` | `Override` | `Deferred` | `Final` | `Infix` | `Local`
3993+
* @param tpe The bounds of the type
3994+
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol.
3995+
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
3996+
* direct or indirect children of the reflection context's owner.
3997+
*/
3998+
@experimental
3999+
// Keep: `flags` doc aligned with QuotesImpl's `validBoundedTypeFlags`
4000+
def newBoundedType(parent: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol
4001+
39664002
/** Definition not available */
39674003
def noSymbol: Symbol
39684004

Diff for: tests/neg-macros/quote-sym-newtype/Macro_1.scala

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//> using options -experimental -Yno-experimental
2+
import scala.quoted.*
3+
4+
inline def testConflictingBounds = ${ testConflictingBoundsImpl }
5+
inline def testConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl }
6+
7+
transparent inline def transparentTestConflictingBounds = ${ testConflictingBoundsImpl }
8+
transparent inline def transparentTestConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl }
9+
10+
11+
def testConflictingBoundsImpl(using Quotes): Expr[Object] = {
12+
import quotes.reflect.*
13+
14+
def makeType(owner: Symbol): Symbol =
15+
// type Foo >: Int <: String
16+
Symbol.newBoundedType(
17+
owner,
18+
"Foo",
19+
Flags.EmptyFlags,
20+
TypeBounds(TypeRepr.of[Int], TypeRepr.of[String]),
21+
Symbol.noSymbol
22+
)
23+
makeClass(makeType)
24+
}
25+
26+
def testConflictingBoundsWithTypeLambdaImpl(using Quotes): Expr[Object] = {
27+
import quotes.reflect.*
28+
def makeType(owner: Symbol): Symbol =
29+
// type Foo >: [X] =>> Int <: Any
30+
Symbol.newBoundedType(
31+
owner,
32+
"Foo",
33+
Flags.EmptyFlags,
34+
TypeBounds(TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int]), TypeRepr.of[Any]),
35+
Symbol.noSymbol
36+
)
37+
makeClass(makeType)
38+
}
39+
40+
def makeClass(using quotes: Quotes)(typeCons: quotes.reflect.Symbol => quotes.reflect.Symbol) = {
41+
import quotes.reflect.*
42+
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => List(typeCons(sym)), None)
43+
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(TypeDef(clsSymbol.typeMember("Foo"))))
44+
45+
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
46+
}
47+

Diff for: tests/neg-macros/quote-sym-newtype/Test_2.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//> using options -experimental -Yno-experimental
2+
def test =
3+
transparentTestConflictingBounds // error
4+
transparentTestConflictingBoundsWithTypeLambda // error
5+
// testConflictingBounds // should throw an error here also, to be implemented before stabilisation
6+
// testConflictingBoundsWithTypeLambda // should throw an error here also, to be implemented before stabilisation
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//> using options -experimental -Yno-experimental
2+
import scala.quoted.*
3+
4+
inline def testMacro = ${ testImpl }
5+
6+
transparent inline def transparentTestMacro = ${ testImpl }
7+
8+
def testImpl(using Quotes): Expr[Object] = {
9+
import quotes.reflect.*
10+
11+
def makeBasicType(owner: Symbol): Symbol =
12+
Symbol.newBoundedType(owner, "tpe", Flags.EmptyFlags, TypeBounds.lower(TypeRepr.of[String]), Symbol.noSymbol)
13+
14+
def makeTypesForClass(owner: Symbol): List[Symbol] =
15+
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int])
16+
List(
17+
makeBasicType(owner),
18+
// type Bla >: Nothing <: [X] =>> Int
19+
Symbol.newBoundedType(
20+
owner,
21+
"tpe1",
22+
Flags.EmptyFlags,
23+
TypeBounds.upper(typeLambda),
24+
Symbol.noSymbol
25+
),
26+
// type Bar >: [X] =>> Int <: [X] =>> Int
27+
Symbol.newBoundedType(
28+
owner,
29+
"tpe2",
30+
Flags.EmptyFlags,
31+
TypeBounds(typeLambda, typeLambda),
32+
Symbol.noSymbol
33+
)
34+
)
35+
36+
val typeDef = TypeDef(makeBasicType(Symbol.spliceOwner))
37+
// Expr printer does not work here, see comment:
38+
// https://github.com/scala/scala3/pull/20347#issuecomment-2096824617
39+
println(typeDef.toString)
40+
assert(typeDef.toString == "TypeDef(tpe,TypeTree[TypeBounds(TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class java)),object lang),String),TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any))])")
41+
42+
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None)
43+
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(
44+
TypeDef(clsSymbol.typeMember("tpe")),
45+
TypeDef(clsSymbol.typeMember("tpe1")),
46+
TypeDef(clsSymbol.typeMember("tpe2")),
47+
))
48+
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
49+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//> using options -experimental -Yno-experimental
2+
def test =
3+
testMacro
4+
transparentTestMacro
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//> using options -experimental -Yno-experimental
2+
import scala.quoted.*
3+
4+
inline def testMacro = ${ testImpl }
5+
6+
transparent inline def transparentTestMacro = ${ testImpl }
7+
8+
def testImpl(using Quotes): Expr[Object] = {
9+
import quotes.reflect.*
10+
11+
def makeBasicType(owner: Symbol): Symbol =
12+
Symbol.newTypeAlias(owner, "tpe", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol)
13+
14+
def makeTypesForClass(owner: Symbol): List[Symbol] =
15+
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int])
16+
List(
17+
makeBasicType(owner),
18+
// type Foo = [X] =>> Int
19+
Symbol.newTypeAlias(
20+
owner,
21+
"tpe1",
22+
Flags.EmptyFlags,
23+
typeLambda,
24+
Symbol.noSymbol
25+
),
26+
)
27+
28+
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None)
29+
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(
30+
TypeDef(clsSymbol.typeMember("tpe")),
31+
TypeDef(clsSymbol.typeMember("tpe1")),
32+
))
33+
34+
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//> using options -experimental -Yno-experimental
2+
def test =
3+
testMacro
4+
transparentTestMacro

Diff for: tests/pos-macros/quote-sym-newtype/Macro_1.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -experimental -Yno-experimental
2+
import scala.quoted.*
3+
4+
inline def testMacro = ${ testImpl }
5+
6+
def testImpl(using Quotes): Expr[Unit] = {
7+
import quotes.reflect.*
8+
val sym = Symbol.newTypeAlias(Symbol.spliceOwner, "mytype", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol)
9+
val typeDef = TypeDef(sym)
10+
assert(typeDef.show == "type mytype = java.lang.String")
11+
12+
Block(List(typeDef), '{()}.asTerm).asExprOf[Unit]
13+
}

Diff for: tests/pos-macros/quote-sym-newtype/Test_2.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
//> using options -experimental -Yno-experimental
2+
def test = testMacro

Diff for: tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ val experimentalDefinitionInLibrary = Set(
6363
"scala.quoted.Quotes.reflectModule.SymbolModule.newModule",
6464
"scala.quoted.Quotes.reflectModule.SymbolModule.freshName",
6565
"scala.quoted.Quotes.reflectModule.SymbolMethods.info",
66+
// Added for 3.6.0, stabilize after feedback.
67+
"scala.quoted.Quotes.reflectModule.SymbolModule.newBoundedType",
68+
"scala.quoted.Quotes.reflectModule.SymbolModule.newTypeAlias",
6669

6770
// New feature: functions with erased parameters.
6871
// Need erasedDefinitions enabled.

0 commit comments

Comments
 (0)