Back to Compose Multiplatform

API Evolution

analysis/docs/contribution-guide/api-evolution.md

2.3.209.4 KB
Original Source

API Evolution

This guide covers the lifecycle of Analysis API endpoints from introduction to deprecation, including stability transitions and migration strategies. For guidance on designing new APIs, see the API Development Guide.

Components of the Analysis API

The Analysis API consists of several interconnected components, each serving a specific purpose:

  • The Kotlin PSI (source, guidelines)

    • Foundation layer providing syntax tree representation through KtElement hierarchy
    • Uses Kt prefix (compared to Ka for Analysis API)
    • Has own stability annotations: @KtExperimentalApi, @KtImplementationDetail, @KtNonPublicApi
    • Key entities: KtFile, KtDeclaration, and KtExpression
  • Analysis API Surface (source)

    • User-facing layer of the Analysis API. Maps the Kotlin PSI to its semantic representation
    • Key entities: KaSession, KaSymbol, KaType
  • Platform Interface (source)

    • Abstraction layer between the API and its execution environments
    • Defines how the API interacts with project structure and file systems
    • Key entities: KotlinPlatformComponent, KaEngineService
  • Analysis API Standalone (source)

    • Command-line implementation for using the API outside of IDEs
    • Provides project structure management that IDEs typically handle automatically
  • Implementations

    • Analysis API implementations
      • K2 implementation, based on the new K2 compiler frontend (source)
      • K1 implementation, based on the classic, K1 compiler frontend (source)
    • Platform interface implementations
      • Kotlin IntelliJ IDEA plugin (source)
      • Analysis API Standalone (source)
    • PSI Implementation, including stubs (source)
    • PSI Reference Implementations (source)
  • Light Classes (source)

    • A Java view for Kotlin declarations designed mainly for Java interoperability
    • Currently, also used in UAST (technical debt)
  • Low-level API (source)

    • K2-specific infrastructure for on-demand and incremental code analysis
    • Not directly accessible to end users

API Stability Categories

Every part of the Analysis API falls under one of these stability categories:

  • Stable API

    • Strong source and binary backward compatibility
    • Changes follow strict deprecation cycles
    • Applies to
      • Analysis API Surface (excluding declarations with opt-ins)
  • Unstable API

    • May evolve without deprecation warnings
    • Changes may follow deprecation cycles, depending on the API usage
    • Applies to
      • Platform Interface
      • Standalone API
      • Declarations annotated with @KaExperimentalApi, @KaNonPublicApi, @KaIdeApi
      • Kotlin PSI is formally not yet stable despite its practical stability
  • Implementation Details

    • No compatibility guarantees between versions
    • Reserved for internal use only
    • Applies to
      • K1 and K2 implementations of the Analysis API
      • Platform Interface implementations
      • Low-level API
      • Light Classes (unless explicitly exposed in the Analysis API Surface)
      • PSI Reference implementations
      • Declarations annotated with @KaImplementationDetail

[!NOTE] PSI uses parallel annotations: @KtExperimentalApi, @KtImplementationDetail, @KtNonPublicApi. The @KtPsiInconsistencyHandling annotation marks code handling inconsistent PSI states (no Analysis API equivalent).

Adding New APIs

New APIs follow a three-phase lifecycle designed to prevent premature stabilization while gathering real-world feedback. This process protects both API designers and users from the cost of breaking changes.

1. Introduction Phase

Goal: Ship the API to early adopters while explicitly communicating its experimental nature.

  • Mark with @KaExperimentalApi
    This annotation serves as both a compiler warning and documentation that the API may change. Users must explicitly opt-in, ensuring they understand the risks.

  • Choose whether to implement for both K1 and K2
    If K1 support is impractical, use @KaK1Unsupported with clear documentation.

  • Provide comprehensive documentation and tests
    Experimental doesn't mean untested. Include edge cases, error conditions, and usage examples. This helps early adopters use the API correctly and provides valuable feedback.

  • Follow the design principles outlined in the API Development Guide
    Experimental APIs should still adhere to design principles. Fixing design issues later is more expensive than getting them right initially.

2. Validation Phase

Goal: Confirm the API design through real usage and identify necessary refinements.

  • Gather usage feedback
    Monitor how developers actually use the API versus intended usage. Look for common workarounds, frequent questions, or misuse patterns that suggest design issues.

  • Ensure idiomatic usage patterns emerge
    The API should feel natural to Kotlin developers. If users consistently need helper functions or complex setup code, consider incorporating these patterns into the API itself.

  • Verify edge case handling
    Real-world usage often reveals edge cases not covered in initial testing. Ensure the API gracefully handles malformed input, incomplete analysis results, and platform-specific variations.

3. Stabilization Phase

Goal: Ensure the API is ready for long-term compatibility commitments.

Before removing @KaExperimentalApi, verify that:

  • API is actively used with positive feedback
    Look for evidence that the API solves real problems efficiently. Lack of usage may indicate the API isn't needed anymore.

  • Naming is consistent and self-explanatory
    Names should align with Analysis API conventions and be understandable without extensive documentation. Consider whether parameter names can be improved based on usage patterns.

  • No anticipated design changes
    The team should have high confidence that the API design will remain stable. Major changes after stabilization require the full deprecation process.

Deprecating APIs

Deprecation follows a careful multi-release cycle with clear migration paths. The process balances giving users time to migrate while preventing indefinite maintenance of obsolete APIs.

1. Initial Deprecation

Goal: Signal the intent to remove the API while providing clear migration guidance.

  • Add the @Deprecated(WARNING) annotation
    This generates compiler warnings that alert users to the deprecation without breaking builds. The warning level ensures existing code continues to compile while encouraging migration.

  • Explain alternatives in the deprecation message and KDoc
    Provide guidance on replacement APIs. Include code examples when the migration is non-trivial. If no direct replacement exists, explain the recommended approach.

  • Update all usages in Kotlin and IntelliJ repositories
    Test the migration by updating internal usage. This serves as real-world examples for external users and ensures the migration path is practical.

  • Document migration in COMPATIBILITY.md
    Mention the deprecation in the compatibility document for smoother communication with the Analysis API users.

2. ERROR Advancement

Goal: Prevent new usage while maintaining both source and binary compatibility for existing code.

  • Ensure there's at least one major release with WARNING
    This gives users a full release cycle to discover and plan their migration.

  • Advance the deprecation level to ERROR
    This prevents new usage while allowing existing code to compile with explicit suppression. Users can still build their code but must acknowledge they're using deprecated APIs that will be removed shortly.

3. Removal or Hiding

Goal: Complete the deprecation process while maintaining ecosystem compatibility.

  • Ensure there's at least one major release with ERROR
    This provides a final grace period for users who need additional time to migrate complex codebases.

  • Choose between complete removal and @Deprecated(HIDDEN)
    Annotating by @Deprecated(HIDDEN) might be useful when the API is still needed for compatibility with already built JARs.