Skip to content

Reduce duplicated logic across FIR -> IR with FIR attributes #1775

@ZacSweers

Description

@ZacSweers

From @bnorm in kotlinlang

While there isn't an "official" way to share information, it is possible to access some FIR elements from IR using the metadata property:
val IrClass.firClass: FirClass? get() = (metadata as? FirMetadataSource.Class)?.fir

Combine that with the ability to add custom data to FIR declarations, you should be able to store information in FIR and access it from IR:
class CustomData(...)
object CustomDataAttribute : FirDeclarationDataKey()

var FirClass.customData: CustomData? by FirDeclarationDataRegistry.data(CustomDataAttribute)
val IrClass.customData: CustomData? get() = (metadata as? FirMetadataSource.Class)?.fir?.customData


Dumped this into an LLM for some inspo in case anyone wants to take some cracks at this


Plan: FIR Attribute Migration for Metro Compiler

This document outlines a plan to use FIR attributes (FirDeclarationDataKey / FirDeclarationDataRegistry) to share precomputed analysis data between the FIR and IR phases of the Metro compiler. This will eliminate redundant scanning and analysis, improving performance and consistency.


Tier 1: High-Impact Candidates

These candidates involve core models that are currently fully recomputed in IR or are accessed extremely frequently.

1. MetroAnnotations Extraction

  • Description: The structured representation of Metro-specific annotations (e.g., @Provides, @Inject, @Scope).
  • Current State: Extensively parsed in AggregationChecker, BindingContainerCallableChecker, and all generators. Re-parsed in DependencyGraphTransformer and BindingLookup using IR annotations.
  • Benefit: Storing MetroAnnotations<MetroFirAnnotation> on FirCallableDeclaration and FirClass eliminates repeated filtering and analysis of the annotations list across both phases.

2. InjectedClass Model & Inject Constructor Resolution

  • Description: Information about constructor-injected classes: which constructor is injectable, assisted injection status, and member injection sites.
  • Current State: findInjectConstructor() is called multiple times across FIR checkers and IR transformers. The InjectedClass model is re-derived in IR with explicit "TODO" comments in InjectConstructorTransformer and MembersInjectorTransformer.
  • Benefit: Centralizes the "injectability" model. IR can simply look up the attribute to find the constructor and members without re-scanning all class declarations.

3. ContextualTypeKey / WrappedType Analysis

  • Description: The "contextual" view of a type, distinguishing between the canonical type and its wrapping layers (e.g., Provider<Lazy<T>>).
  • Current State: asFirContextualTypeKey and asContextualTypeKey perform nearly identical recursive analysis in FIR and IR respectively.
  • Benefit: Attaching the resolved contextual key/WrappedType structure to parameters and return types avoids repeated structural analysis of complex generic types.

4. BindingContainer & Provider Callable Metadata

  • Description: Metadata for @BindingContainer classes and their provider callables, including includes, should-generate-object flags, and parameter type keys.
  • Current State: Re-scanned in BindingContainerTransformer and IrBindingContainerResolver to build transitive closures. Currently uses a synthetic @CallableMetadata annotation for some bridging.
  • Benefit: Pre-identifying containers and their contents in FIR speeds up IR graph construction and eliminates the need for IR member scans.

Tier 2: Medium-Impact Candidates

These candidates involve specific metadata resolution that is currently duplicated but has a more localized impact.

5. Parameters & Parameter Models (including Assisted Keys)

  • Description: Unified representation of a callable's parameters, including FirTypeKey, assisted identifiers, and flags.
  • Current State: Computed in generators to scaffold declarations and re-derived in IR to implement bodies.
  • Benefit: Carrying the Parameters model ensures consistency and avoids re-resolving "contextual" types or assisted strings.

6. Contribution & Scope Metadata

  • Description: Resolved scope ClassIds, rank, and replaces targets for @ContributesBinding and related annotations.
  • Current State: Resolution often requires a custom type resolver. Doing this once in FIR and storing it simplifies the complex merging logic in IrContributionMerger.
  • Benefit: Avoids re-reading scope annotations and re-resolving target classes in IR.

7. GraphNode Categorization & Creator Info

  • Description: Classification of graph members (accessors, injectors, extensions) and SAM function signatures for creators.
  • Current State: Analyzed in FIR for diagnostics and re-scanned from all class members in GraphNodes.Builder.build in IR.
  • Benefit: Storing the member category as an attribute on the callable symbol avoids a full re-scan of graph members in IR.

8. Qualifier Annotation

  • Description: Resolved qualifier meta-annotations.
  • Current State: Searched in fir.kt and re-extracted during IrTypeKey construction.
  • Benefit: Centralizes qualifier resolution logic.

Tier 3: Lower Priority / Already Partially Handled

  • Binding Container Status: Recursive check for isBindingContainer() can be cached as a boolean.
  • Include/Exclusion Lists: Pre-resolved ClassId lists for @DependencyGraph includes/excludes.
  • Member Injection Map: Already partially bridged via MemberInjectClass.toProto() for cross-compilation, but FIR attributes would help the in-compilation path.

Recommended Implementation Order

  1. InjectedClass (Tier 1, Basic component processing #2): Highest priority as IR code already has TODO comments requesting this and it touches core injection logic.
  2. Inject Constructor Resolution: Simple attribute shape (single constructor reference) with high frequency of use.
  3. MetroAnnotations (Tier 1, Constructor injection factory gen #1): Broadest impact across all checkers and transformers.
  4. ContextualTypeKey/WrappedType (Tier 1, Support generic types in constructor injection factory gen #3): Eliminates the most duplicated algorithm.
  5. Iterate through Tier 2 items.

Implementation Pattern

1. Define the attribute key

object InjectedClassDataKey : FirDeclarationDataKey()

var FirClass.injectedClassData: InjectedClass?
    by FirDeclarationDataRegistry.data(InjectedClassDataKey)

2. Store in FIR (during generator or checker)

declaration.injectedClassData = computedInjectedClass

3. Read in IR

val IrClass.injectedClassData: InjectedClass?
    get() = (metadata as? FirMetadataSource.Class)?.fir?.injectedClassData

Metadata

Metadata

Assignees

No one assigned

    Labels

    PR welcomeIndicates PRs are welcome for this issuecompiler:FIRAnything pertaining to the compiler frontendcompiler:IRAnything pertaining to the compiler backendgood first issueGood for newcomers

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions