Skip to content

Latest commit

 

History

History
136 lines (89 loc) · 5.48 KB

File metadata and controls

136 lines (89 loc) · 5.48 KB

Symbol Export Project Guidelines

Project Overview

Symbol Export is a Kotlin compiler plugin and associated tooling that allows you to export Kotlin symbols (classes, functions, properties, etc.) from one module and use them in another module. This is particularly useful for:

  • Creating type-safe references to Kotlin symbols across module boundaries
  • Enabling code generation based on annotated symbols
  • Providing a way to reference Kotlin symbols without direct dependencies

Project Structure

  • gradle/build-logic - The build logic for the project
  • annotations - Contains annotations that are shared between the compiler plugin and the runtime
  • compiler-plugin - The Kotlin compiler plugin that processes annotations and exports symbols
  • gradle-plugin - The Gradle plugin that applies the compiler plugin to Kotlin compilations
  • generator - The code generator used by the Gradle plugin to generate symbol classes
  • names-internal - The internal symbols objects used to pass serialized symbols between the compiler plugin and the code generator
  • symbols - The public symbols API that defines the structure of exported symbols
  • symbols-* - Symbol conversion extensions for different platforms/libraries
  • test - A build that tests symbol export and import functionality

Core Concepts

Annotations

The project defines several annotations in the annotations module.

Symbol Types

The symbols module defines the structure of exported symbols.

Export Process

  1. The Gradle plugin applies the compiler plugin to Kotlin compilations
  2. The compiler plugin identifies symbols marked with export annotations
  3. The compiler plugin validates the symbols and writes them to a JSON file
  4. The generator reads the JSON file and generates Kotlin code with objects representing the exported symbols
  5. The generated code can be used in other modules to reference the exported symbols

General Guidelines

Use the JetBrains MCP (Machine Coding Protocol) whenever possible except for running tests, e.g., for things like searching for symbols or running builds.

Always use Gradle to do builds and run tests.

Test Running Instructions

When running multiple tests, run the Gradle test task (or the particular test task needed) once, rather than running it multiple times for individual tests.

Always run tests using the Run-Tests interaction, not using Gradle via the command line or using the jetbrains MCP.

Kotlin Compiler Plugin Development

Resources

General compiler plugin instructions can be found in the links in the readme here.

Instructions on testing Kotlin compiler plugins can be found here.

Compiler Plugin Tests

The compiler plugin tests live in compiler-plugin/src/testData - these are effectively Kotlin templates that either include diagnostics or test FIR/IR dumps.

  • The .kt files are the templates
  • The .fir.txt and .fir.ir.txt files are snapshot files generated by the tests - don't try to add them manually
  • The .kt files don't always conform to proper Kotlin code
  • They are pre-processed using the pre-processors in compiler-plugin/src/testFixtures/kotlin/dev/rnett/symbolexport/support

New tests with snapshot files will always fail the first time due to creating those files.

The setup for these tests lives in compiler-plugin/src/testFixtures. They are run using the usual :compiler-plugin:test task. The :compiler-plugin:generateTests task generates the test classes. Symbol-related imports and Kotlin test imports, when available, are automatically added (see TestConfigurations).

Each directory of test files has a corresponding test in /test-gen. These are created by GenerateTests.kt's main function.

Testing Diagnostics

To test a diagnostic, create a Kotlin file that would trigger it and include the diagnostic ID in the file. For example, for the diagnostic SYMBOL_EXPORT_PARENT_MUST_BE_EXPOSED:

Without diagnostic:

@Module
abstract class TestModule() : BaseModule() {
    @Default
    constructor(t: Int) : this()
}

With diagnostic on secondary constructor:

@Module
abstract class TestModule() : BaseModule() {
    <!SYMBOL_EXPORT_PARENT_MUST_BE_EXPOSED!>
    @Default
    constructor(t: Int) : this()<!>
}

The <!> marks the end of the source range the diagnostic is on.

Common Tasks

Exporting Symbols

To export symbols from a module:

  1. Apply the symbol-export Gradle plugin to your module
  2. Add the annotations dependency to your module
  3. Mark symbols for export using the appropriate annotations
  4. Build your module to generate the exported symbols

Using Exported Symbols

To use exported symbols in another module:

  1. Add a dependency on the module with exported symbols
  2. Import the generated Symbols class
  3. Use the symbols to reference the original declarations

Troubleshooting

Common issues and solutions:

  • Missing symbols: Ensure that all parent declarations are marked with either @ExportSymbol or @ChildrenExported
  • Visibility errors: Exported symbols must be public or marked with @PublishedApi
  • Local declaration errors: Local declarations cannot be exported
  • Test failures: New tests with snapshot files will fail the first time due to creating those files

Misc

  • Only run the JVM tests, ignore other platforms
  • Run the common tests on the JVM target to test them. Do not run them on other targets.