Files

Fir elements

  • All fir elements are listed in 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
  • 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

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.
  • 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