Skip to content

Commit

Permalink
Add the possibility to create a typeSymbol in the Quotes API (#20347)
Browse files Browse the repository at this point in the history
Closes #19448
  • Loading branch information
hamzaremmal authored Oct 8, 2024
2 parents 35c7d74 + 5d06f96 commit a3015f4
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 0 deletions.
17 changes: 17 additions & 0 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2649,6 +2649,16 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
def newBind(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol =
checkValidFlags(flags.toTermFlags, Flags.validBindFlags)
dotc.core.Symbols.newSymbol(owner, name.toTermName, flags | dotc.core.Flags.Case, tpe)

def newTypeAlias(owner: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol =
checkValidFlags(flags.toTypeFlags, Flags.validTypeAliasFlags)
assert(!tpe.isInstanceOf[Types.TypeBounds], "Passed `tpe` into newTypeAlias should not represent TypeBounds")
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags, dotc.core.Types.TypeAlias(tpe), privateWithin)

def newBoundedType(owner: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol =
checkValidFlags(flags.toTypeFlags, Flags.validBoundedTypeFlags)
dotc.core.Symbols.newSymbol(owner, name.toTypeName, flags | dotc.core.Flags.Deferred, tpe, privateWithin)

def noSymbol: Symbol = dotc.core.Symbols.NoSymbol

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

// Keep: aligned with Quotes's `newBind` doc
private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased

// Keep: aligned with Quotes's 'newBoundedType' doc
private[QuotesImpl] def validBoundedTypeFlags: Flags = Private | Protected | Override | Deferred | Final | Infix | Local

// Keep: aligned with Quotes's `newTypeAlias` doc
private[QuotesImpl] def validTypeAliasFlags: Flags = Private | Protected | Override | Final | Infix | Local

end Flags

given FlagsMethods: FlagsMethods with
Expand Down
36 changes: 36 additions & 0 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3963,6 +3963,42 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
// Keep: `flags` doc aligned with QuotesImpl's `validBindFlags`
def newBind(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr): Symbol

/** Generate a new type symbol for a type alias with the given parent, name and type
*
* This symbol starts without an accompanying definition.
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
* this symbol to the TypeDef constructor.
*
* @param parent The owner of the type
* @param name The name of the type
* @param flags extra flags to with which symbol can be constructed. Can be `Private` | `Protected` | `Override` | `Final` | `Infix` | `Local`
* @param tpe The rhs the type alias
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol.
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
* direct or indirect children of the reflection context's owner.
*/
@experimental
// Keep: `flags` doc aligned with QuotesImpl's `validTypeAliasFlags`
def newTypeAlias(parent: Symbol, name: String, flags: Flags, tpe: TypeRepr, privateWithin: Symbol): Symbol

/** Generate a new type symbol for a type bounds with the given parent, name and type
*
* This symbol starts without an accompanying definition.
* It is the meta-programmer's responsibility to provide exactly one corresponding definition by passing
* this symbol to the TypeDef constructor.
*
* @param parent The owner of the type
* @param name The name of the type
* @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`
* @param tpe The bounds of the type
* @param privateWithin the symbol within which this new type symbol should be private. May be noSymbol.
* @note As a macro can only splice code into the point at which it is expanded, all generated symbols must be
* direct or indirect children of the reflection context's owner.
*/
@experimental
// Keep: `flags` doc aligned with QuotesImpl's `validBoundedTypeFlags`
def newBoundedType(parent: Symbol, name: String, flags: Flags, tpe: TypeBounds, privateWithin: Symbol): Symbol

/** Definition not available */
def noSymbol: Symbol

Expand Down
47 changes: 47 additions & 0 deletions tests/neg-macros/quote-sym-newtype/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//> using options -experimental -Yno-experimental
import scala.quoted.*

inline def testConflictingBounds = ${ testConflictingBoundsImpl }
inline def testConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl }

transparent inline def transparentTestConflictingBounds = ${ testConflictingBoundsImpl }
transparent inline def transparentTestConflictingBoundsWithTypeLambda = ${ testConflictingBoundsWithTypeLambdaImpl }


def testConflictingBoundsImpl(using Quotes): Expr[Object] = {
import quotes.reflect.*

def makeType(owner: Symbol): Symbol =
// type Foo >: Int <: String
Symbol.newBoundedType(
owner,
"Foo",
Flags.EmptyFlags,
TypeBounds(TypeRepr.of[Int], TypeRepr.of[String]),
Symbol.noSymbol
)
makeClass(makeType)
}

def testConflictingBoundsWithTypeLambdaImpl(using Quotes): Expr[Object] = {
import quotes.reflect.*
def makeType(owner: Symbol): Symbol =
// type Foo >: [X] =>> Int <: Any
Symbol.newBoundedType(
owner,
"Foo",
Flags.EmptyFlags,
TypeBounds(TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int]), TypeRepr.of[Any]),
Symbol.noSymbol
)
makeClass(makeType)
}

def makeClass(using quotes: Quotes)(typeCons: quotes.reflect.Symbol => quotes.reflect.Symbol) = {
import quotes.reflect.*
val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => List(typeCons(sym)), None)
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(TypeDef(clsSymbol.typeMember("Foo"))))

Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
}

6 changes: 6 additions & 0 deletions tests/neg-macros/quote-sym-newtype/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//> using options -experimental -Yno-experimental
def test =
transparentTestConflictingBounds // error
transparentTestConflictingBoundsWithTypeLambda // error
// testConflictingBounds // should throw an error here also, to be implemented before stabilisation
// testConflictingBoundsWithTypeLambda // should throw an error here also, to be implemented before stabilisation
49 changes: 49 additions & 0 deletions tests/pos-macros/quote-sym-newboundedtype/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//> using options -experimental -Yno-experimental
import scala.quoted.*

inline def testMacro = ${ testImpl }

transparent inline def transparentTestMacro = ${ testImpl }

def testImpl(using Quotes): Expr[Object] = {
import quotes.reflect.*

def makeBasicType(owner: Symbol): Symbol =
Symbol.newBoundedType(owner, "tpe", Flags.EmptyFlags, TypeBounds.lower(TypeRepr.of[String]), Symbol.noSymbol)

def makeTypesForClass(owner: Symbol): List[Symbol] =
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int])
List(
makeBasicType(owner),
// type Bla >: Nothing <: [X] =>> Int
Symbol.newBoundedType(
owner,
"tpe1",
Flags.EmptyFlags,
TypeBounds.upper(typeLambda),
Symbol.noSymbol
),
// type Bar >: [X] =>> Int <: [X] =>> Int
Symbol.newBoundedType(
owner,
"tpe2",
Flags.EmptyFlags,
TypeBounds(typeLambda, typeLambda),
Symbol.noSymbol
)
)

val typeDef = TypeDef(makeBasicType(Symbol.spliceOwner))
// Expr printer does not work here, see comment:
// https://github.com/scala/scala3/pull/20347#issuecomment-2096824617
println(typeDef.toString)
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))])")

val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None)
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(
TypeDef(clsSymbol.typeMember("tpe")),
TypeDef(clsSymbol.typeMember("tpe1")),
TypeDef(clsSymbol.typeMember("tpe2")),
))
Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
}
4 changes: 4 additions & 0 deletions tests/pos-macros/quote-sym-newboundedtype/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//> using options -experimental -Yno-experimental
def test =
testMacro
transparentTestMacro
35 changes: 35 additions & 0 deletions tests/pos-macros/quote-sym-newtype-in-trait/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//> using options -experimental -Yno-experimental
import scala.quoted.*

inline def testMacro = ${ testImpl }

transparent inline def transparentTestMacro = ${ testImpl }

def testImpl(using Quotes): Expr[Object] = {
import quotes.reflect.*

def makeBasicType(owner: Symbol): Symbol =
Symbol.newTypeAlias(owner, "tpe", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol)

def makeTypesForClass(owner: Symbol): List[Symbol] =
val typeLambda = TypeLambda.apply(List("X"), _ => List(TypeBounds.empty), _ => TypeRepr.of[Int])
List(
makeBasicType(owner),
// type Foo = [X] =>> Int
Symbol.newTypeAlias(
owner,
"tpe1",
Flags.EmptyFlags,
typeLambda,
Symbol.noSymbol
),
)

val clsSymbol = Symbol.newClass(Symbol.spliceOwner, "CLS", List(TypeRepr.of[Object]), sym => makeTypesForClass(sym), None)
val classDef: ClassDef = ClassDef(clsSymbol, List(TypeTree.of[Object]), List(
TypeDef(clsSymbol.typeMember("tpe")),
TypeDef(clsSymbol.typeMember("tpe1")),
))

Block(List(classDef), Apply(Select(New(TypeIdent(clsSymbol)), clsSymbol.primaryConstructor), List.empty)).asExprOf[Object]
}
4 changes: 4 additions & 0 deletions tests/pos-macros/quote-sym-newtype-in-trait/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//> using options -experimental -Yno-experimental
def test =
testMacro
transparentTestMacro
13 changes: 13 additions & 0 deletions tests/pos-macros/quote-sym-newtype/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//> using options -experimental -Yno-experimental
import scala.quoted.*

inline def testMacro = ${ testImpl }

def testImpl(using Quotes): Expr[Unit] = {
import quotes.reflect.*
val sym = Symbol.newTypeAlias(Symbol.spliceOwner, "mytype", Flags.EmptyFlags, TypeRepr.of[String], Symbol.noSymbol)
val typeDef = TypeDef(sym)
assert(typeDef.show == "type mytype = java.lang.String")

Block(List(typeDef), '{()}.asTerm).asExprOf[Unit]
}
2 changes: 2 additions & 0 deletions tests/pos-macros/quote-sym-newtype/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//> using options -experimental -Yno-experimental
def test = testMacro
3 changes: 3 additions & 0 deletions tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ val experimentalDefinitionInLibrary = Set(
"scala.quoted.Quotes.reflectModule.SymbolModule.newModule",
"scala.quoted.Quotes.reflectModule.SymbolModule.freshName",
"scala.quoted.Quotes.reflectModule.SymbolMethods.info",
// Added for 3.6.0, stabilize after feedback.
"scala.quoted.Quotes.reflectModule.SymbolModule.newBoundedType",
"scala.quoted.Quotes.reflectModule.SymbolModule.newTypeAlias",

// New feature: functions with erased parameters.
// Need erasedDefinitions enabled.
Expand Down

0 comments on commit a3015f4

Please sign in to comment.