389f02b016
^KT-65337 Fixed
Fir elements
- All fir elements are listed in
FirTreeBuilder.kt. - The syntax for declaring a new element:
element(elementName, elementKind: Kind, vararg parents: Element).elementNameis a name of the declared element. IfelementName = Foothen it's class will be calledFirFoo.kinddescribes target package of an element. Available kinds:Expression(packagefir.expression)Declaration(packagefir.declaration)Reference(packagefir.references)TypeRef(packagefir.types)Other(packagefir)
- if not a single parent element was declared, then the generated element will be a direct inheritor of
FirElement.
Types
- Types commonly used in configuration are listed in
Types.kt - There are multiple ways to describe a new type:
fun <reified T : Any> type()uses FQN of the correspondingTclass.fun type(packageName: String, typeName: String, exactPackage: Boolean = false, kind: TypeKind = TypeKind.Interface).- if
exactPackage = false, its return type with default package prefix:org.jetbrains.kotlin.packageName.typeName. - otherwise, there is no default prefix:
packageName.typeName.
- if
generatedType([packageName: String], typeName: String)— same astype(packageName, typeName)but with theorg.jetbrains.kotlin.firprefix .
Content of elements
- Fields of elements are described in
NodeConfigurator.kt. - Syntax:
elementName.configure {
// node configuration
}
- Fields:
- The
Fieldclass describes a field of an element. - There are multiple ways of creating new fields, but they have similar syntax:
field(..., nullable: Boolean = false, withReplace: Boolean).- if
nullableis true, then the type of the field will be nullable. - if
withReplaceis true, then in the element thereplace...method will be generated for that field. - in place of
...you can pass an optional name (withStringtype), andTypeReforElementobject - if no
nameis passed, then it will be inferred based on the type. - if
TypeReforElementhave type arguments, then you can useTypeRef.withArgs(vararg types: TypeRef).
- if
- Also, you can create fields with lists of some types.
- Syntax:
fieldList([name: String], element: ElementOrRef)(if no name is specified, it will be inferred based on the type ofelement).
- Syntax:
- And there are helper functions for fields of primitive types:
booleanField,intField,stringField. - If you want to generate a separate
transform...function for the field, you should callwithTransform()on it. - To add the field to the node being configured, you should call the infix
+operator:+fieldList("catches", catchClause).withTransform(). - Also, you can use
symbol(symbolTypeName: String)to create a field namedsymbolwith a lying in theorg.jetbrains.kotlin.fir.symbolspackage. - Some predefined fields are listed in
FieldSets.kt.
- The
- If your node has some
transform...methods, and you want to add methods for transforming all other children, you should callneedTransformOtherChildren(). - If an element has type parameters, you should declare them using
withArg(typeParameterName: String, [upperBound: TypeRef]). - If an element inherits another element with type parameters, you should match those parameters with concrete types using
parentArgs(parent: Element, typeParameterName: String, vararg arguments: Pair<String, TypeRef>). - Note that if some element contains type parameters, it should be configured before its inheritors (will be fixed later).
Implementations
- If an element has no inheritors, then it will have a default implementation. Otherwise, you should declare an implementation that you want.
- All implementations are described in
ImplementationConfigurator.kt - Syntax:
impl(element: Element, [name: String]) {...}describes the configuration of the element with namename(if there is no name, then it would beElementTypeImpl). Lambda with implementation configuration is optional. Note that this function returns an object of typeImplementation.noImpl(element: Element)used when you don't want to generate any implementation forelement/
- In the configuration lambda you can:
- Describe the kind of the implementation —
FinalClass(default),OpenClass,AbstractClass,Interfaceusing the syntaxkind = Interface - Add parents for the implementation class
- syntax:
parents += parent. parentcan be only implementation withkind = Interface.
- syntax:
- Configure default values for fields:
default(fieldName: String) { ... }- in configuration lambda you can describe:
value = _defaultValue_.withGetter = true/false(falseby default).delegate = delegateFieldName(used for generating such fields:val typeRef: FirTypeRef get() = expression.typeRef).delegateName = fieldNameInDelegateType(val expressionTypeRef: FirTypeRef get() = expression.typeRef).needAcceptAndTransform = true/false(trueby default) -- specify it if you don't want to accept field inacceptChildren.customSetter = setterExpresison.
- note that by default, all fields with fir elements are mutable, and others are immutable.
- in configuration lambda you can describe:
- Also, there are some aliases for that default:
default(fieldName, value)defaultNull(fieldName, [withGetter: Boolean])
- If some fields should be
lateinit, you describe them in thelateinit(vararg fields: String)call. - If you use some types that should be imported, list them by calling
additionalImports(vararg types: Importable)
- Describe the kind of the implementation —
Notes
- There is an algorithm that automatically makes as most abstract classes instead of interfaces as possible.
If you want to some
ElementorImplementationshould be always an interface you should:- call
shouldBeAnInterface()when configuring aElementinNodeConfigurator.kt - specify
kind = Interfacewhen configuring anImplementationinImplementationConfigurator.kt
- call