Repository navigation
docs(emitters): state Kotlin's reserved sets for units - #1335
Merged
Merged
Conversation
An anyOf lets one value satisfy several variants, and the emitter design lowered every untagged union to a closed sum that holds exactly one. Where two variants admit the same value, holding the first misnames it and drops the members only the others declare. The plan now proves, once per union, whether two variants of an anyOf can admit one value (TypePlan.Inclusive), by JSON kind, by finite value sets and by a member one model requires that the other cannot hold. A union it proves disjoint lowers as a closed sum, as a oneOf does. One it cannot is inclusive: it holds an optional member per variant, set for each variant the value admits, decodes into every candidate variant, and encodes the merge, a disagreement being an encode error. A null a nullable variant admits is held by the reference, never by a variant. Go lowers an inclusive union to a struct with a pointer per variant and Kotlin to a class with a nullable property per variant. T-29 pins the round trip of a value two open models admit.
A positional model is an array on the wire, so the kinds rule now says so and the member rule reads only models laid out by name. A stream that ends on a terminal variant now ends on an element holding one, among the several an inclusive union holds. A null literal variant now lowers as a nullable variant does, so either spelling of a null member reaches the reference. Inclusive joins the plan fields the plan-or-refine test keeps, and the docs emitter says when a value may be several variants at once.
plan.Policy held a function, PrimaryResponse, and Plan.Version hashed the policy. A function has no stable bytes: json/v2 refuses it, its printed form is an address that changes every run, and skipping it gives two policies that pick different responses one version. The policy is now data from a closed vocabulary: PrimaryResponse is an ordered list of rules and DefaultFails says whether an unpicked Default is the catch-all failure. Version hashes the document's digest without sources, a new plan.Revision bumped with every change to Build's output, and the policy's deterministic JSON. EnvelopeDetect named a type nothing defined. plan.Policy.Envelopes now lists envelopes a configuration names, none by default. Each matches a body by the bases it reaches or by the members it declares, and says where the value sits and which flag value means the call failed. A matched success returns that member, keeps the envelope reachable through WithResponse, and turns a 2xx whose flag says it failed into a failure with no fault or kind, decoded as the operation's matching error body. Failures and pages are read whole. T-30 pins the plan's identity and T-31 the envelope reads.
…into docs/emitters-envelopes-policy-data # Conflicts: # docs/emitters/docs-and-mock.md # docs/emitters/testing/mandatory-tests.md
Runtime policy was one tree per project, so an operation needing its own timeout or retry had nowhere to say so, and a document's own SDK configuration, kept verbatim as vendor extensions, would have been parsed by each emitter its own way. Policy and hints are now layers, each holding a document-wide override and overrides by operation: what a document embeds, the project's, the emitter's. A setting takes the highest layer that sets it, an operation's value over its layer's document-wide one, and a declared IR fact beats every layer. A new emitters/embed reader turns vendor extensions a configuration maps into the lowest layer of each, decoding each value strictly and reporting what it cannot read. The vocabulary gains connection-error retries, Retry-After, a throttling backoff and redirects. Whether an operation is safe or idempotent is a plan fact, OpPlan.Effects: declared, else from its HTTP method. An enum a client reads keeps a value it does not list, as a union does, and a oneOf value two variants admit decodes as plan.Policy.ExclusiveMatch says. A service hint makes a parameter a client setting; nothing is hoisted by inference, since a shared parameter stops being shared as an API grows, and the plan only reports candidates.
An enum whose value type has no Go constant form, a date, a uuid or a decimal, had no Go lowering, and the type table built an enum value by decoding its wire form, which no constant can be. Kotlin's enum class writes every member as its @SerialName string, so a boolean or numeric enum went out as a string its schema does not admit. A member is now written as the JSON kind its value type has, and decoded by comparing as JSON compares values, numbers by value. Go declares an enum over its value type's row, and over the wire text where the row has no constant, the codec writing the value type's JSON kind and a Value method giving the row's type. Kotlin generates a KSerializer for every enum that is not a string enum, and an entry of a mapped type holds its value as that type. T-36 pins every value type on every target.
Kotlin's naming document stated only the members it gives every type, so a Kotlin emitter had no names to reserve for a group's module or for the declarations a unit holds beside the spec's, and T-19 no fixture. Measured with Kotlin 2.4.0 and Gradle 9.8.2: a module named build sits in the root project's output, which clean deletes with the module's sources; one named buildsrc is taken for Gradle's buildSrc on a case-insensitive file system and fails the build; gradle and src share the wrapper's and the root sources' directories. Those four are the units set, and a group's module is placed at the build root so the set applies. Hard keywords need no reservation: backticked as package segments they compile on the JVM, JS and Native and link into a framework, and kotlin is refused only as the first segment, which the options name. The unit set is Client, a group's sub-client and the runtime names the root unit declares.
This was referenced Nov 2, 2026
EmitterInput.Policy became policy.Resolved when policy gained layers and per-operation overrides, but the refiner context still held a single policy.Policy, so a step had no way to read an operation's own settings. Ctx.Policy now holds the resolved policy the emitter is given.
…o docs/emitters-enum-value-types
…into docs/emitters-kotlin-reserved-sets
The rule saying what a oneOf decodes a value two variants admit to landed at the end of the section on a variant's null, which it has nothing to do with. It belongs with how a variant is recognized, which is also where T-35 links it.
…o docs/emitters-enum-value-types
…into docs/emitters-kotlin-reserved-sets
…into docs/emitters-envelopes-policy-data
…ata' into docs/emitters-policy-layers
…o docs/emitters-enum-value-types
…into docs/emitters-kotlin-reserved-sets
…into docs/emitters-envelopes-policy-data
…ata' into docs/emitters-policy-layers
…o docs/emitters-enum-value-types
…into docs/emitters-kotlin-reserved-sets
A group's module sat at the build's root, so the units set had to hold every name Gradle and its plugins keep there. The set as written missed two names and a letter case. A module named api shares its directory with the root project's ABI dump, which the Kotlin Gradle plugin writes to api/. One named gradlew makes `gradle wrapper` fail. And a case-insensitive file system meets build in any letter case: `clean` deletes a module named Build, sources and all. Any plugin may claim another name there, so no list closes the set. Each module now sits in modules/<name>. A Kotlin package name does not depend on its directory, so moving modules there renames nothing. Under modules/, modules named build, buildsrc, BuildSrc, gradle, gradlew, src and api all build, and `clean` and `wrapper` keep their sources. What a module's project still meets is two Gradle rules on project names. Gradle refuses buildSrc as a project's name wherever it sits. A module named as the root project, under the root's Maven group, has the root's coordinates, and the build fails on a cycle. Measured with Kotlin 2.4.0 and Gradle 9.8.2 on darwin/arm64.
The unit set put every runtime name in the root unit. Under SplitByGroup
or Partition no runtime can sit there: the root holds the client, which
reaches every group's module, and a group module using a runtime in the
root closes a cycle Gradle refuses ("Circular dependency between the
following tasks"). No document says where the runtime lands, so the set
now reserves what a runtime template declares in whichever unit it
renders into beside the spec's names, as the template guards already
hold it, and lists those names as examples, adding EnvelopedResponse,
which the list left out. Where the runtime lands is a decision of its
own.
The Objective-C export drops a top-level declaration's package, so a
Multiplatform framework gives every unit's declarations one namespace
and appends _ to one of two that share a name. With Kotlin 2.4.0 the
runtime's ApiException in p.runtime was exported as ApiException_ beside
a model p.accounts.ApiException, so a Swift caller catching ApiException
catches the model. On Multiplatform the target's own names are now
reserved in every unit, a top-level name two units declare is one the
export would mangle, and output units no longer says two units'
declarations never collide.
T-19 named no type after a reserved name, though only a type meets the
unit scope. It now names types in the root and in a group's unit, and
holds the framework's header to the Swift names the policy chose.
A name the options give a unit never yields, so where one meets a
reserved or refused name nothing escaped it and nothing refused it.
A partition key may map to a name the units' scope reserves: shared,
which the shared unit holds, a Kotlin module's buildSrc, or a Go package
named internal, which no caller outside the SDK may import. Such a key
is now an options error, beside the other partition keys the document
cannot honour, and T-18's fixtures for every partition error draw it.
The Kotlin root's first segment was left to the options to name, with
nothing to refuse what kotlinc refuses there ("Only the Kotlin standard
library is allowed to use the 'kotlin' package"). The JVM refuses a
first segment java the same way, though kotlinc compiles it: loading the
class throws "SecurityException: Prohibited package name". A root
beginning with either is now an options error, as an invalid value is.
The unit set reserves what a runtime template declares in whichever unit it renders into, since no document places the runtime and the root cannot hold it once groups or partitions split the SDK. Say where that choice is tracked, so a reader of the set knows it is open.
The Swift rule said only a top-level name two units declare shares the framework's namespace. The export also joins a nested class's name to its outer's, and those joined names sit in the same namespace. With Kotlin 2.4.0, a model PetDog was exported to Swift as PetDog_ beside the class Dog that a sealed interface Pet nests, which Swift calls PetDog too. A model UserBuilder was exported to Objective-C as UserBuilder_ beside User.Builder, whose Swift name keeps its nesting. The target nests public classes of its own in types: a model's Builder and Companion, a union's Unknown fallback. Their joined names (UserBuilder, UserCompanion, PaymentUnknown) are now reserved in every unit on Multiplatform, beside the top-level names already reserved, and a name another declaration exports is one the export would mangle.
Windows refuses con, prn, aux, nul, and com or lpt with a digit, as the name of a file or directory in any letter case and whatever the extension. A Kotlin unit named aux could not be checked out or built there: its module directory, and the directory kotlinc writes the package's classes to, cannot be made. A top-level class named Aux fails the same way, as the JVM writes it to Aux.class. Go's own module checks refuse the same list on any host (aux.go and the import path example.com/sdk/aux are both malformed to golang.org/x/mod), which is the same rule seen from another toolchain. Both sets now hold those names, a root the options give with one as a segment is an options error, since an options name never yields, and T-19 requires that no path a Kotlin build writes has one for a segment, which a build on a host that allows them would otherwise pass. These names come from Windows' file-naming rules, not from a build on Windows, and the section now says so.
This was referenced Nov 4, 2026
The unit set said the target's names are reserved in every unit on Multiplatform. A reserved name takes Kotlin's escape, so a model UserBuilder, which meets User.Builder only in the Objective-C export, would have become UserBuilder_ for every caller, the JVM's included. The Swift rule already gives such a declaration an explicit Objective-C and Swift name instead. The set now reserves these names in the Objective-C namespace alone, and says a declaration meeting one only there keeps its Kotlin name.
The Multiplatform clause reserved "each of these" in the Objective-C namespace, which since the previous change also took in the Windows device names, a file-system rule with no meaning there. It now names what it means: each name the target declares.
No document said how a Kotlin unit's name is cased, and every units comparison turns on it. A camel-cased segment would let two units differ in letter case alone, customHostname beside customhostname, which a case-insensitive file system makes one module directory and one class directory. A unit's package segment is now its words in lowercase, joined with no separator, as Go's package names are, so two units whose words break differently render alike and collide, and are told apart by ID as output units already says.
The naming document said a hard keyword as a package segment takes the backtick escape, from 18 of Kotlin's 28 hard keywords. The other ten (break, continue, do, else, false, for, if, typeof, var, while), backticked, also compile in a package and an import on the JVM. All 49 soft and modifier keywords (value, data, import, internal and the rest) compile there unescaped. The paragraph now says every hard keyword was measured, and that soft and modifier keywords need no escape.
The Swift rule gives a name the export would mangle an explicit
@ObjCName. In Kotlin 2.4.0 that annotation is still experimental, and a
file using it without an opt-in fails to compile ("This declaration
needs opt-in ... ExperimentalObjCName"). With
@file:OptIn(ExperimentalObjCName::class) it works: a model
p.accounts.ApiException named AccountsApiException left the runtime's
ApiException its own name. A caller in another package compiled without
opting in, and no signature carries the annotation, so the language
floor's rule against opt-in APIs in public signatures holds. The rule
now says so.
The flattening it guards against was measured in one module only. It
holds across modules too: with the runtime in its own Gradle module
under modules/ and a group module exporting a model of the same name,
both exported into one framework, the runtime's class was the one
renamed ApiException_. The rule now says it was measured both ways, and
the namespace facts sit in a bullet of their own.
Reserved names defined the units' scope as the names a target's toolchain treats apart, and a unit's as the names the target declares there. Kotlin's sets now hold Windows' device names in both: a unit named aux, or a top-level class Aux written to Aux.class, is a path Windows refuses. Neither definition admitted them, nor did the inputs EscapeReservedWords lists. The units' scope now also covers what a file system the target writes to treats apart, and a unit's what a top-level declaration cannot take, and the step's inputs follow.
A top-level declaration shadows a Kotlin default import for its whole
package. With a model List in a package, List<String> there failed to
compile ("No type arguments expected for 'class List : Any'"), and with
a model Exception, a class extending Exception did, as did Map<String,
Long> beside a model Map and Result<Int> beside a model Result. Error
and Result are among the commonest schema names, and nothing reserved
them.
The unit set now reserves each name Kotlin's default imports give that
any refine step or template can write. Each declares those it writes as
it declares what it binds, and the template lint holds a template to
them. The set is their union for the target's version, never what one
unit's code happens to write, so adding an operation renames no model.
The shared definition of a unit's scope now names this case, and the
unit bullet is split by where each reserved name comes from.
A property named class, which Kotlin backticks, was probed too and needs
nothing: its getter is String getClass(), a descriptor apart from
Object.getClass(), and it compiles and runs.
A Swift caller has Swift's standard library and Foundation in scope
beside the framework, and the Objective-C export renames neither side.
Typechecked with Swift 6.2.3 against a Kotlin 2.4.0 framework, a class
the framework exports as Optional, Result or Duration shadowed Swift's
own in the caller's file: Optional<Int> gave "cannot specialize
non-generic type 'Optional'", and Duration.seconds(1) gave "type
'Duration' has no member 'seconds'". One exported as Decimal, Date, Data
or URL made the name ambiguous with Foundation's ("'Decimal' is
ambiguous for type lookup in this context"). Importing the SDK broke
code that never touched it, and the runtime itself declares Optional,
Duration and, on Multiplatform, Decimal, so every Multiplatform SDK did
this.
Every type name Swift's standard library and Foundation declare is now
reserved for the Swift names a framework exports, a table of the options
version as the naming tables are. A declaration whose Swift name would
be one of them takes an explicit one, the runtime's three included. The
lowering table's naming row says so, and T-19 compiles a Swift file that
imports the framework and Foundation.
The Objective-C export declares classes of its own under the framework's prefix: ProbeBase, ProbeMutableSet, ProbeMutableDictionary, ProbeNumber and a box per Kotlin number (KotlinInt in Swift, ProbeInt in Objective-C). Measured with Kotlin 2.4.0, a spec class Base, MutableSet or MutableDictionary was renamed beside them (ProbeBase_), but one named Number, Boolean, Byte, Short, Int, Long, Float, Double or an unsigned number's took the same Objective-C name, and the framework failed to link: "Linking globals named 'OBJC_METACLASS_$_ProbeNumber': symbol multiply defined!". Removing each in turn named all twelve. With an explicit @ObjCName on the spec's class it linked. These names are now reserved in the Objective-C namespace, the Multiplatform part of the unit set is split into its Objective-C and Swift halves, and T-19 requires that the framework links.
A Swift caller settles an ambiguity by naming the module, Probe.Image or SwiftUI.Image, which the Swift rules lean on for every module not reserved. That fails once a type takes a module's name. Typechecked with Swift 6.2.3, a class Probe in the framework Probe turned Probe.Client() into "type 'Probe' has no member 'Client'", and a class Foundation turned Foundation.Date into "'Date' is not a member type of class 'Probe.Foundation'". The framework's own name, Swift and Foundation are now reserved among the Swift names a caller sees. A module a caller imports by choice is left to that qualification, and the rule now says so where the next reader looks: in a SwiftUI file, let i: Image was ambiguous beside a framework class Image, while SwiftUI.Image and Probe.Image both resolved and SwiftUI's own Image and Color expressions compiled. T-19's Swift file also qualifies a name by each module. The lowering table's naming row listed two of the cases that take an explicit Swift name, and missed the rest, so it now points at the Swift rules instead.
OmarAlJarrah
added a commit
that referenced
this pull request
Nov 4, 2026
…ft-keywords main's tree at dfd9fe0, #1459's squash, is the tree of the head of fix/openapi-declared-dialect the previous merge brought in, so what main adds is #1335's docs/emitters alone. The conflicts the two histories draw, both carrying #1459's changes from 71e08b6, resolve to this branch's side: outside docs/emitters the merge's tree is the branch's, and inside it main's.
This was referenced Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/emitters/refine/reserved-names.mdleaves three scopes to each target. Kotlin's naming document stated only the type scope, so a Kotlin emitter had nothing to reserve for a unit's module or package, or for what the SDK declares beside the spec's names in a unit, and T-19 had no fixture to name. Each answer below comes from a build, not from documentation.Kotlin's modules sit in
modules/<name>, not at the build's root. A module at the root meets every name Gradle and its plugins keep there, and that set has no fixed end:buildis the root project's output: the module builds, butgradle cleanon the root deletes its sources. On a case-insensitive file system such as macOS's the same happens to a module namedBuild.buildsrcfails the build on macOS, where Gradle finds it asbuildSrc, its build-logic build, and runs:buildSrc:compileKotlin.gradlewmakesgradle wrapperfail ("Cannot write a file to a location pointing at a directory").apishares its directory with the root project's ABI dump, which the Kotlin Gradle plugin's ABI validation writes toapi/and surface verification reads.gradleandsrcshare theirs with the wrapper's files and the root's sources.A Kotlin package name does not depend on its directory, so nesting the modules renames nothing. Under
modules/, modules namedbuild,buildsrc,BuildSrc,gradle,gradlew,srcandapiall build, andcleanandwrapperkeep their sources.The units:
buildSrc, the root project's name, and Windows' device names. These are what a module's project still meets:buildSrcas a project's name wherever its directory is ("'buildSrc' cannot be used as a project name as it is a reserved name"). Its check is"buildSrc".equals(name), sobuildsrcandBuildSrcpass, and it is the only reserved project name in Gradle 9.8.2.group, has the root's coordinates, so Gradle resolves the root's dependency on it to the root itself ("Circular dependency between the following tasks"). This happens at the root and undermodules/, and with a distinct group the same build passes.con,prn,aux,nul, andcomorlptwith a digit as a file or directory name, in any letter case and whatever the extension. So neither a module namedauxnor the directory kotlinc writes its package's classes to could be made there. These come from Windows' file-naming rules, not from a build on Windows, and the document says so. Go's own module checks refuse the same list on any host.A unit's package segment is its words in lowercase, joined with no separator (
useraccounts), as Go's package names are. No document said how Kotlin cases one, and every units comparison turns on it. A camel-cased segment would letcustomHostnameandcustomhostnamebe two units that a case-insensitive file system makes one directory. Lowercase makes them render alike and collide, and the collision is resolved by ID.Not units names: hard keywords,
kotlinandjava.packageand animporton the JVM.fun,object,when,is,inandtypealiasalso compile for JS and macOS Native and link into an Objective-C framework. All 49 soft and modifier keywords (value,data,import,internal…) compile there unescaped. Keywords take the existing escape.kotlinas a package's first segment ("Only the Kotlin standard library is allowed to use the 'kotlin' package"). The JVM refusesjavathere: kotlinc compilespackage java.probe, and loading the class throwsSecurityException: Prohibited package name: java.probe. Only a name the options give can begin a package, so a root beginning with either is now an options error.A unit:
Client, each sub-client, what a runtime template declares beside the spec's names, the default imports the generated code writes, and the Windows device names. The template guards already hold a template to the names it declares, so the set comes from the templates.ApiFailure, the exception tree,Response,EnvelopedResponse,RawBody,Optional,Transportand the rest are listed as examples, not kept as a second list. No document places the runtime, and the root cannot hold it: the root holds the client, which reaches every group's module, so a runtime there closes a cycle underSplitByGrouporPartitionthat Gradle and Go both refuse. That choice is #1489's. The set says so and does not depend on it. The device names are reserved here as well, since the JVM writes a top-level class to a file of its name (Aux.class).A top-level declaration also shadows a Kotlin default import for its whole package. With a model
Listin a package,List<String>there failed to compile ("No type arguments expected for 'class List : Any'"), as didMap<String, Long>beside a modelMap,Result<Int>beside a modelResult, and a class extendingExceptionbeside a modelException. The unit set now reserves each default-imported name any refine step or template can write. Each declares those it writes, and the template lint holds a template to them. The set is their union for the target's version, so adding an operation renames no model.On Multiplatform the target's names are reserved in the Objective-C namespace of every unit. The Objective-C export drops a declaration's package and joins a nested class's name to its outer's. So a framework gives every unit's declarations, nested ones among them, one namespace, and of two sharing a name it appends
_to one:ApiExceptioninp.runtimebeside a modelp.accounts.ApiException, the export named the runtime's exceptionApiException_, so a Swift caller catchingApiExceptionwould catch the model.PetDogexported to Swift asPetDog_beside the classDogthat a sealed interfacePetnests, which Swift also callsPetDog. A union's fallback,Payment.Unknown, is such a class.UserBuilderexported to Objective-C asUserBuilder_besideUser.Builder, whose Swift name keeps its nesting.So the joined names of the public classes the target nests in a type (
UserBuilder,UserCompanion,PaymentUnknown) are reserved too. The flattening holds across Gradle modules: with the runtime in its own module undermodules/and a group module declaring a modelApiException, both exported into one framework, the runtime's class was again the one renamed. The remedy, an explicit@ObjCName, is experimental in Kotlin 2.4.0 and fails to compile without an opt-in. With@file:OptIn(ExperimentalObjCName::class)the spec's class exported asAccountsApiExceptionand the runtime keptApiException. A caller in another package compiled without opting in, soswift.mdnow says each such file opts in.Swift's and Foundation's own names are reserved for the Swift names a framework exports. A Swift caller has both in scope, and the export renames neither side. Typechecked with Swift 6.2.3 against a Kotlin 2.4.0 framework:
Optional,ResultorDurationshadowed Swift's own in the caller's file:cannot specialize non-generic type 'Optional',type 'Duration' has no member 'seconds'.Decimal,Date,DataorURLmade the name ambiguous with Foundation's:'Decimal' is ambiguous for type lookup in this context.Importing the SDK broke code that never touched it. The runtime itself declares
Optional,Durationand, on Multiplatform,Decimal, so every Multiplatform SDK did this. Those three now take Swift names of their own, and the names are a table of the options version.A caller's module qualification keeps working. A Swift caller settles an ambiguity by naming the module (
Probe.Image,SwiftUI.Image), and that fails once a type takes a module's name. A classProbein the frameworkProbeturnedProbe.Client()intotype 'Probe' has no member 'Client', and a classFoundationturnedFoundation.Dateinto'Date' is not a member type of class 'Probe.Foundation'. The framework's own name,SwiftandFoundationare now reserved among the Swift names. A module a caller imports by choice is left to that qualification, which the rule now states as its boundary. In a SwiftUI file,let i: Imagewas ambiguous beside a framework classImage, whileSwiftUI.ImageandProbe.Imageresolved and SwiftUI's ownImage(systemName:)andColor.redcompiled.The export's own Objective-C names are reserved. The export declares classes under the framework's prefix:
ProbeBase,ProbeMutableSet,ProbeMutableDictionary,ProbeNumberand a box per Kotlin number. A spec classBase,MutableSetorMutableDictionarywas renamed beside them (ProbeBase_). One namedNumber,Boolean,Byte,Short,Int,Long,Float,Doubleor an unsigned number's took the same Objective-C name, and the framework failed to link:Linking globals named 'OBJC_METACLASS_$_ProbeNumber': symbol multiply defined!. Removing each in turn named all twelve, and an explicit@ObjCNamelinked. T-19 now requires that the framework links. A declaration that meets one only in that namespace keeps its Kotlin name and takes an explicit Objective-C and Swift name, so no JVM name moves for Swift's sake.swift.mdnow counts a name another declaration exports among the names the export would mangle, andoutput-units.mdno longer says two units' declarations never collide.A unit name the options give that cannot yield is refused. A partition key mapping to a name the units' scope reserves (
shared, a Kotlin module'sbuildSrc, a Go package'sinternal) was neither escaped nor refused. It is now an options error, which T-18's partition fixtures draw.The shared scopes now admit what a file system refuses and what a default import supplies.
reserved-names.mddefined the units' scope as what "a target's toolchain treats apart", and a unit's as the names the target declares there. Neither admitted Windows' device names or a shadowed default import, nor did the inputsEscapeReservedWordslists insteps.md. They now do.T-19 now names types after the reserved names, in the root and in a group's unit, since only a type meets the unit scope. It names groups after every units name and every name a Kotlin build keeps at its root. It requires that
cleanandwrapperkeep every module's sources, and that no path a Kotlin build writes has a Windows device name for a segment, which a build on macOS or Linux would otherwise pass. It also requires that the framework's header give each declaration the Swift name the naming policy chose, none of them a Swift or Foundation type, so that a Swift file importing the framework and Foundation compiles. Every template's declared names are held to the default-imported names it writes.On the golden spec (spec @70c631aa, morphic @71e08b61), the 595 groups by first tag normalize to 593 names. Of those,
sharedmeets the shared reservation, and none meetsbuildSrcor a Windows device name, nor does any type. No named type meets a runtime name, a Kotlin default import, a Swift or Foundation type, an export's own class name or a module's name, and none meets a nested class's joined name: the one name ending inBuilder,email-sending_EmailBuilder, has no modelemail-sending_Emailbeside it. One group,api(4 operations, among themPOST /v1/verify), would have shared the root's ABI dump directory had modules sat at the root.Closes #1187
Out of scope
targets/kotlin/naming.mdpoints there.main(a program, which nothing imports) andinit(importable only under an alias): docs/emitters: Go's units set misses main and init #1490.Setup,SetUp). No rule makes them collide, yet the engine refuses their paths case-folded, and on Kotlin/JVM they share one class file (NoClassDefFoundError: p/Setup (wrong name: p/SetUp)). One pair of the golden spec's models does this: docs/emitters: two names that differ only in letter case are not a collision #1491.com.acme.Response(200)inside a class with a propertycomgivesUnresolved reference 'acme'): docs/emitters: a Kotlin member named as a package's first segment breaks a qualified reference #1496.Test plan
package probe.kotlincompiles,package kotlin.probefails with the error above, andpackage java.probecompiles and throwsSecurityExceptionwhen loaded.compileKotlinJvm,compileKotlinJs,compileKotlinMacosArm64andlinkDebugFrameworkMacosArm64, and the header declares all 6 classes. Same-named classes in two packages export asApiException/ApiException_andUser/User_. With the runtime's class inp.runtimeand a model inp.accounts, the runtime's was the one renamed.gradle,src,sharedandfunbuild.buildandBuildbuild, andcleanremoves their sources.buildsrcfails, with and without a plugin version in its build file.gradlewbuilds, andwrapperfails.apibuilds, and the root's ABI dump lands atapi/<root>.api.modules/:build,buildsrc,BuildSrc,gradle,gradlew,srcandapibuild, andcleanandwrapperkeep every module's sources.buildSrcis refused as a project name.modules/, and passes with a group of its own. A module namedmodulesbuilds undermodules/.User.Builder,User.Companionand the sealedPet.Dog, beside top-levelUserBuilder,PetDogandp.accounts.UserCompanion, names them as above.modules/, both exported into one framework:compileKotlinJvmandlinkDebugFrameworkMacosArm64pass, and the runtime'sApiExceptionexports asApiException_.@ObjCNamewithout an opt-in fails to compile, and with a file-level@OptInthe explicit name holds and a caller elsewhere needs none.List,Map,ExceptionandResultin a package breakList<String>,Map<String, Long>, a subclass ofExceptionandResult<Int>there. A property namedclasscompiles and runs, its getterString getClass()apart fromObject.getClass().Optional,Result,Duration,Decimal,Date,DataandURLfails as quoted above in a type annotation of each of the seven, whileDate(),Data()andURL(string:)as expressions still meant Foundation's.Number,Boolean,Byte,Short,Int,Long,UByte,UShort,UInt,ULong,FloatandDoublefails to link, each named in turn by the linker, and links once they are gone, whileBase,MutableSetandMutableDictionaryexport asProbeBase_,ProbeMutableSet_andProbeMutableDictionary_.@ObjCNameonNumberlinks. A Swift file qualifyingProbe.Client()andFoundation.Datefails beside classesProbeandFoundation, and classesTypeandProtocolcause nothing.modules/, modules namedkotlin,java,test,main,clean,help,tasks,init,dependencies,projects,properties,wrapper,assemble,checkandjarbuild, andcleanandwrappersucceed.golang.org/x/mod/modulerefusesaux.go,Aux.go,con.goandlpt1.goand the import pathexample.com/sdk/aux, and acceptsaux_test.go. Go 1.27.3 refuses to build a module importingexample.com/sdk/auxon darwin. A root project holdingClientand the runtime, with a module throwing the runtime's exception, fails on a cycle. Go 1.27.3 refuses the same layout (import cycle not allowed).