Rust's proc_macro API. Kotlin's tokenizer underneath.
The Kotlin Multiplatform port of Rust's compiler-internal
proc_macro crate — the in-tree
crate that rustc makes available to procedural macros and that
proc_macro2 dispatches to via its
Compiler variant. We keep the surface API faithful to the upstream Rust
crate so Kotlin ports of syn, quote, serde_derive, async-trait,
starlark_derive, logos-codegen, and the rest of the proc-macro
ecosystem can consume it without surprise. We back that surface with
JetBrains' multiplatform Kotlin lexer + parser
(org.jetbrains.kotlin.kmp.lexer.KotlinLexer, KtTokens, KotlinParser)
so the tokens carry real spans into real Kotlin source.
Both upstreams are Apache 2.0. This repo is Apache 2.0. The licensing path is clean for either depending on the JetBrains KMP-parsing artifact or vendoring the pieces we need.
-
Public API: faithful to upstream
proc_macro. Every public type (TokenStream,Span,Group,Delimiter,Ident,Punct,Spacing,Literal,TokenTree,LexError,token_stream::IntoIter) matches the Rust crate's shape. KDoc translates the upstream///comments. The translation rules in workspace-rootCLAUDE.mdand this repo's ownAGENTS.mdapply (Rustsnake_case→ KotlinlowerCamelCase,Vec<T>→List<T>, lifetimes dropped, etc.) — but the API contract upstream callers see isproc_macro's. -
Implementation: backed by Kotlin's tokenizer.
TokenStream::new,TokenStream::from_str,Span::call_site, etc. don't sit on a hand-rolled Rust-source lexer (that'sproc-macro2-kotlin's Fallback job). They sit onKotlinLexer+KtTokens+ the multiplatformSyntaxTreeBuilderpipeline, producing tokens that carry actual Kotlin-source positions.
proc-macro2-kotlin shipped only the Fallback half of the
Compiler / Fallback split that proc_macro2's wrapper.rs defines.
There was no Compiler half because Kotlin doesn't have a compiler-supplied
token-stream crate in the rustc-bridged sense — Kotlin's plugin
extension points (FIR FirDeclarationGenerationExtension, IR
IrGenerationExtension, kapt, KSP) trade in symbols and IR, not tokens.
But Kotlin does ship a portable lexer/parser pair at
compiler/multiplatform-parsing/. That lexer produces a real token stream
over real Kotlin source. Once we wrap it in the same surface shapes
proc_macro2 exposes, we have the missing Compiler half — and a lot more
besides.
-
A real Compiler variant for
proc-macro2-kotlin. Itswrapper.rsdispatch layer becomes two-variant in earnest: Fallback keeps doing Rust-source tokenization for tests / standalone codegen, Compiler delegates here for Kotlin-source-aware work.Detection.kt'sinsideProcMacro()gets a non-trivial meaning: "we have a Kotlin lexer available on this target." -
A Kotlin-emitter substrate for
lalrpop-kotlin.lalrpop-kotlinalready reaches Rust-output byte parity. The natural next step is a Kotlin emitter on the same parser tables. Aquote!-style Kotlin emitter needs a tokenizer that knows Kotlin keywords, string templates (OPEN_QUOTE/CLOSING_QUOTE/ interpolation entries),?./!!,val/var,fun-modifier forms, etc. — i.e.KotlinLexer+KtTokens. Wrap that inproc_macro2-shaped types here and the emitter has its tokenizer. -
A Rust → Kotlin source-level translation bridge. Pipeline reads: Rust source →
proc-macro2-kotlin(Fallback, Rust-shaped) →syn-kotlinAST → transliteration pass →proc-macro-kotlin(Compiler, Kotlin-shaped) → emitted.ktfiles validated againstKotlinLexer. The kotlinmania porting workflow becomes a library pipeline instead of a hand transliteration. -
A foundation for a Kotlin parser via
starlark-kotlin.starlark-kotlinports the Starlark expression language. A Kotlin parser expressed as Starlark rules over this repo's token stream becomes tractable in a way it wasn't when the token surface didn't exist.
| Concern | proc-macro2-kotlin |
proc-macro-kotlin |
|---|---|---|
| Upstream Rust crate | proc-macro2 |
proc_macro (rustc in-tree) |
| Role in upstream | the standalone fallback + the public API | the compiler-supplied backend |
| Token vocabulary | Rust-shaped | Rust-shaped (same surface) |
| Source text accepted | Rust (via the fallback lexer) | Kotlin (via KotlinLexer) |
| Span data | synthetic byte ranges in a process-wide source map | real KtTokens syntax-element spans |
| Status | published / pre-publish maintenance | scaffolded |
proc-macro2-kotlin continues to be the public API surface that
downstream crates (syn-kotlin, quote-kotlin, the Kotlin ports of
serde_derive, async-trait, starlark_derive, logos-codegen, …)
depend on. proc-macro-kotlin is the alternative backend wired in
through proc-macro2-kotlin's wrapper layer — never imported directly by
downstream ports.
The order is: faithful Rust API first, weld in the Kotlin backend second. Concretely:
-
Pull the upstream
proc_macrosource intotmp/. Target isrust-lang/rust:library/proc_macro/src/, shallow-cloned and pinned. The crate'sbridgesubmodule (the FFI layer that talks torustc's expansion process) does not port — it has no Kotlin analog. Every other public type does. -
Stand up the Gradle Multiplatform build. Same target list as
sibling
*-kotlinrepos: macOS arm64, Linux x64, mingw-x64, iOS arm64 / x64 / simulator-arm64, JS, Wasm-JS, Android. Same Kotlin/JS security-hardening template from workspaceCLAUDE.md. -
Resolve the JetBrains KMP-parsing dependency. Pin Maven
coordinates for
org.jetbrains.kotlin.kmp.lexer.*(currently@ApiStatus.Experimentalin the kotlin/kotlin tree). Decide depend vs vendor based on artifact availability on every target. -
Port the public types bottom-up.
Delimiter,Spacing,Span,LexError,Ident,Punct,Literal,Group,TokenTree,TokenStream,token_stream::IntoIter. Each gets aport-lint: sourceheader pointing at its upstream.rsfile. Bodies start as the most faithful translation possible; backend wiring lands as a second pass. -
Weld in the Kotlin tokenizer.
TokenStream::from_str(fromStringin Kotlin) callsKotlinLexerand adapts itsSyntaxElementTypeoutput into the Rust-shapedTokenTreevariants.Spancarries real text offsets.Group's delimiters mapLBRACE/RBRACE/LPAR/RPAR/LBRACKET/RBRACKETtoDelimiter.Brace/Parenthesis/Bracket. -
Re-enable
proc-macro2-kotlin's wrapper layer. Restore the two-variantWrapperTokenStream/WrapperSpan/ etc. that the in-flightport/refaithful-divergent-translationsbranch collapsed, with the Compiler arms now delegating here. -
Publish to Maven Central behind
proc-macro2-kotlin 0.2.0's release. The two ship together.
Scaffold only. README, LICENSE, .gitignore. No source files yet, no
Gradle build yet, no published artifact. The next commit lands the build
template + workspace docs (AGENTS.md / CLAUDE.md / NEXT_ACTIONS.md) and
the upstream tmp/proc-macro/ fetch script.
Apache 2.0. Upstream proc_macro is dual-licensed MIT / Apache 2.0; the
JetBrains Kotlin compiler sources we depend on are Apache 2.0; this repo
takes the intersection.