100 lines
6.5 KiB
Markdown
100 lines
6.5 KiB
Markdown
# Fir elements
|
|
|
|
- All fir elements are listed in [`FirTreeBuilder.kt`](src/org/jetbrains/kotlin/fir/tree/generator/FirTreeBuilder.kt).
|
|
- The syntax for declaring a new element: `element(elementName, elementKind: Kind, vararg parents: Element)`.
|
|
- `elementName` is a name of the declared element. If `elementName = Foo` then it's class will be called `FirFoo`.
|
|
- `kind` describes target package of an element. Available kinds:
|
|
- `Expression` (package `fir.expression`)
|
|
- `Declaration` (package `fir.declaration`)
|
|
- `Reference` (package `fir.references`)
|
|
- `TypeRef` (package `fir.types`)
|
|
- `Other` (package `fir`)
|
|
- 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`](src/org/jetbrains/kotlin/fir/tree/generator/Types.kt)
|
|
- There are multiple ways to describe a new type:
|
|
- `fun <reified T : Any> type()` uses FQN of the corresponding `T` class.
|
|
- `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` .
|
|
- `generatedType([packageName: String], typeName: String)` — same as `type(packageName, typeName)` but with
|
|
the `org.jetbrains.kotlin.fir` prefix .
|
|
|
|
# Content of elements
|
|
|
|
- Fields of elements are described in [`NodeConfigurator.kt`](src/org/jetbrains/kotlin/fir/tree/generator/NodeConfigurator.kt).
|
|
- Syntax:
|
|
```
|
|
elementName.configure {
|
|
// node configuration
|
|
}
|
|
```
|
|
- **Fields:**
|
|
- The `Field` class 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 `nullable` is true, then the type of the field will be nullable.
|
|
- if `withReplace` is true, then in the element the `replace...` method will be generated for that field.
|
|
- in place of `...` you can pass an optional name (with `String` type), and `TypeRef` or `Element` object
|
|
- if no `name` is passed, then it will be inferred based on the type.
|
|
- if `TypeRef` or `Element` have type arguments, then you can use `TypeRef.withArgs(vararg types: TypeRef)`.
|
|
- 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 of
|
|
`element`).
|
|
- 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 call `withTransform()` 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 named `symbol` with a lying in
|
|
the `org.jetbrains.kotlin.fir.symbols` package.
|
|
- Some predefined fields are listed in [`FieldSets.kt`](src/org/jetbrains/kotlin/fir/tree/generator/FieldSets.kt).
|
|
- If your node has some `transform...` methods, and you want to add methods for transforming all other children, you should call
|
|
`needTransformOtherChildren()`.
|
|
- 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 name `name` (if there is no name,
|
|
then it would be `ElementTypeImpl`).
|
|
Lambda with implementation configuration is optional.
|
|
Note that this function returns an object of type `Implementation`.
|
|
- `noImpl(element: Element)` used when you don't want to generate any implementation for `element`/
|
|
- In the configuration lambda you can:
|
|
- Describe the kind of the implementation — `FinalClass` (default), `OpenClass`, `AbstractClass`, `Interface` using the syntax
|
|
`kind = Interface`
|
|
- Add parents for the implementation class
|
|
- syntax: `parents += parent`.
|
|
- `parent` can be only implementation with `kind = Interface`.
|
|
- Configure default values for fields:
|
|
- `default(fieldName: String) { ... }`
|
|
- in configuration lambda you can describe:
|
|
- `value = _defaultValue_`.
|
|
- `withGetter = true/false` (`false` by 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` (`true` by default) -- specify it if you don't want to accept field in
|
|
`acceptChildren`.
|
|
- `customSetter = setterExpresison`.
|
|
- note that by default, all fields with fir elements are mutable, and others are immutable.
|
|
- 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 the `lateinit(vararg fields: String)` call.
|
|
- If you use some types that should be imported, list them by calling `additionalImports(vararg types: Importable)`
|
|
|
|
# Notes
|
|
|
|
- There is an algorithm that automatically makes as most abstract classes instead of interfaces as possible.
|
|
If you want to some `Element` or `Implementation` should be always an interface you should:
|
|
- call `shouldBeAnInterface()` when configuring a `Element` in `NodeConfigurator.kt`
|
|
- specify `kind = Interface` when configuring an `Implementation` in `ImplementationConfigurator.kt`
|