Initial SwiftExtract version - a reusable Swift analysis module#774
Merged
Conversation
ac9071e to
e23ae2c
Compare
This will take a while to finish up but this introduces a new core foundational module called `SwiftExtract` that allows for analysis of swift code into an analysis result that then source generators -- such as swift-java can use. This is to prepare reuse from other language generators, with a strong well maintianed analysis core that swift-java has spearheaded here. This is a massive "move stuff around" so reviewing everyting might be hard but the long term benefit will be massive.
Introduce SwiftExtractConfiguration protocol (neutral AccessLevelMode / Logger.Level) so SwiftExtract no longer depends on SwiftJavaConfigurationShared; Configuration conforms via JExtractSwiftLib/Configuration+SwiftExtract.swift. Gate operator extraction behind config.extractsOperators (default off -> Java unchanged). Expose ExtractedNominalType.declAttributes / declGroupSyntax. All SwiftExtractTests + JExtractSwiftTests pass.
…zer extraction Two opt-in, language-neutral knobs on `SwiftExtractConfiguration` (both default to the prior behavior, so the Java path is unchanged): - `availableImportModules: Set<String>` — module names treated as importable when resolving `#if canImport(<module>)`. The analyzer wraps its build configuration in an `ImportOverlayBuildConfiguration` so declarations guarded behind `#if canImport(MyModule)` are extracted (e.g. another language code generator may declare its runtime module importable here, even when the static build config doesn't otherwise know about it). - `extractsGenericTypeInitializers: Bool` — extract initializers of generic nominal types even when not specialized. swift-java skips these by default (an open generic isn't directly constructible); other language code generators that specialize generics in a post-analysis pass need the base type's initializers available to clone onto the specialization.
The Foundation/FoundationEssentials known-module source files declared Data, Date, and UUID, but URL was missing — so any user code using URL (e.g. `func getHost(url: URL)`) failed to import with an unresolved-type warning, dropping the function silently. Other consumers of the language-neutral analyzer rely on URL for bridging tests; add it alongside the other Foundation built-ins, declaring just the failable `init(string:)` and `absoluteString` property the language-neutral analyzer needs to resolve uses.
…Modules `DefaultSwiftExtractConfiguration` exposed `extractsOperators` and `availableImportModules` as stored properties with init parameters but omitted `extractsGenericTypeInitializers`, so callers using the default config could not opt into it programmatically and silently fell back to the protocol-extension default of `false`. Add it as a stored property and init parameter alongside the other two. Also add four targeted tests in `AnalysisResultTests` exercising the non-default paths of the two analysis-shaping knobs: - `unspecializedGenericInitializersAreSkippedByDefault` and `extractsGenericTypeInitializersKeepsBaseInitializers` confirm the base `Tank<Fish>` flips between 0 and 2 initializers as the knob toggles. - `canImportGuardedDeclsAreSkippedWhenModuleNotAvailable` and `availableImportModulesActivatesCanImportClause` confirm `#if canImport(MadeUpModule)`-guarded types are extracted only when the module is listed in `availableImportModules`.
The `Resources/dummy.json` placeholder exists only so SwiftPM emits a
`Bundle.module` for the SwiftExtract target — the real
`static-build-config.json` is generated at build time by
`_StaticBuildConfigPlugin`. Note that on the `.process("Resources")`
line so a future reader doesn't try to delete the empty-looking file.
… on tests Three small review-driven cleanups: - `SwiftAnalyzer` doc: drop the parenthetical example in the lead-in and fix "language-neutral" to "output is language-neutral"; drop the "useful for tests / no code generation" sentence on the static `analyze` convenience. - `SwiftExtractConfiguration`: remove the protocol-extension defaults for `extractsOperators` and `extractsGenericTypeInitializers`. Both are semantic decisions about what the analysis layer should do for a given language target; making conformers state their position keeps a new language code generator from silently inheriting the Java-specific defaults. `Configuration` (swift-java) now declares both as `false` explicitly. - `SwiftExtractTests`: drop the `.swiftLanguageMode(.v5)` override; the target compiles and runs cleanly under the package's default Swift 6 mode.
…hared Reviewer asked: "why does swift-java need to map at all, can it not use this exact enum?" — yes, after introducing a small shared target. `SwiftJavaConfigurationShared` is intentionally lightweight (stdlib + Foundation only): it must be symlinked into each plugin's source tree because SwiftPM plugins can't have target dependencies. Pulling SwiftSyntax in via `SwiftExtract` would balloon plugin builds. Instead, introduce a sibling `SwiftExtractConfigurationShared` target that holds nothing but the small `AccessLevelMode` enum. Both `SwiftExtract` and `SwiftJavaConfigurationShared` depend on it; the plugin symlink discipline (`Plugins/PluginsShared/SwiftExtractConfigurationShared`) mirrors the existing `SwiftJavaConfigurationShared` symlink. Effects: - `AccessLevelMode` is the single, shared enum. swift-java's `Configuration` uses it directly via `@_exported import`, retiring `JExtractMinimumAccessLevelMode` and the four-line mapping switch in `Configuration+SwiftExtract.swift` (now an identity passthrough). - `SwiftJavaConfigurationShared/Configuration.swift` guards the import with `#if canImport(SwiftExtractConfigurationShared)` so plugin builds (which inline the file alongside `AccessLevelMode.swift` via symlink rather than as a separate module) still compile. - `AccessLevelMode` gains `@nonexhaustive` (SE-0487, gated with `#if compiler(>=6.2)`) per reviewer request, so adding cases in the future is non-breaking. `Codable` conformance is added so it can replace `JExtractMinimumAccessLevelMode` in the on-disk `Configuration` JSON without changing the wire format. - The CLI's `@Option var minimumInputAccessLevelMode` and the `ExpressibleByArgument` conformance switch over to `AccessLevelMode` accordingly.
Two small additions to make CodePrinter usable from downstream targets that compare generated output against goldens: - Mark CodePrinter / PrintMode / PrinterTerminator as Sendable so the type can flow through Sendable-checked contexts (e.g. an actor-bound generator). The state is plain value-type data with no reference fields, so the conformance is unconditional. - Add an `emitSourceLocations: Bool = true` instance flag. When false, a `.sloc` terminator collapses to a plain newline instead of appending ` // function @ file:line`. Default-true preserves the existing tracing behavior; downstream targets that diff generated output against goldens can flip it off to get clean output.
Add an opt-in path for downstream language code generators that treat
unresolved type names symbolically rather than dropping the enclosing
declaration.
`SwiftExtract` defaults to a strict policy: a parameter / return /
property type that the symbol table can't resolve causes the enclosing
`SwiftFunctionSignature` constructor to throw, and the analyzer drops
the whole declaration with a `[warning] Failed to import: …` log line.
That's correct for Java/JNI, where the generator can't render code
referencing an unresolved Swift type.
Other language code generators that resolve the name later — associated
types in a protocol requirement before carrier substitution; a property
type that names a generic parameter to be replaced during specialization;
an external type bridged by simple name — can opt-in by setting
`SwiftExtractConfiguration.permitsUnresolvedTypeReferences = true`. Name
lookups that fail then synthesize a nominal via
`SwiftSyntheticTypes.unresolvedNominal(_:)` so the enclosing declaration
survives and a downstream pass can substitute or recognize the placeholder.
API additions:
- SwiftExtractConfiguration: `var permitsUnresolvedTypeReferences: Bool`
with a default of `false` in the protocol extension. The
DefaultSwiftExtractConfiguration init grows a matching parameter.
- SwiftTypeLookupContext: `var permitsUnresolvedTypeReferences: Bool`
(false by default), populated by SwiftAnalyzer.prepareForTranslation
from the config.
- SwiftSyntheticTypes.unresolvedNominal(_:moduleName:): public utility
that mints a SwiftType.nominal from a bare name by parsing a throwaway
`struct \(name) {}` to obtain the syntax node SwiftNominalTypeDeclaration
requires. The synthetic moduleName is `__SwiftExtractSynthesized` so
downstream code can route around these when needed.
Default behavior (existing consumers) is unchanged: 30/30 SwiftExtract
tests still pass.
…n specs
Add a public seam so downstream language code generators can drive
generic-type specialization from configuration sources other than Swift
typealiases (e.g. a `specialize:` config entry that names a base type
by qualified name) and have those specializations participate in
deferred-constrained-extension matching alongside the analyzer's
natively-registered ones.
Why it matters:
swift-java's analyzer registers a specialization the moment it walks a
`typealias Alias = Base<Args…>` decl, then `applyPendingSpecializations`
drives `findMatchingSpecializations` against the deferred constrained
extensions queue (`extension Box where T == ConcreteT { … }`).
Downstream targets that materialize their own specializations
*after* analysis (because the typealias has an attribute the analyzer
doesn't recognize, or because the spec lives in JSON config) miss the
window — the deferred queue runs against an empty registry and the
constrained-extension methods are silently dropped.
API additions:
- `SwiftAnalyzer.analyze(beforeProcessingDeferredExtensions:)` —
instance and static overloads. The hook fires after the per-source
walk has populated the registry from in-source typealiases, but
before deferred-extension processing. The default-arg overloads
preserve existing call sites.
- `SwiftAnalyzer.registerSpecialization(baseQualifiedName:outputName:typeArgs:)` —
public API that resolves the base by qualified name through the
symbol table, calls `ExtractedNominalType.specialize`, and inserts
into `translator.specializations`. Returns nil when the base
can't be found or isn't generic.
Default behavior (existing consumers calling the no-hook overload) is
unchanged: 30/30 SwiftExtract tests still pass.
Hoist the access-level filter and the operator-skip rule out of SwiftExtract and into each decider. The protocol becomes a single Bool answer instead of the prior tri-state Bool? override on top of a precomputed accessLevelPasses bit. - ExtractDecider.shouldExtract(decl:in:log:) -> Bool; deciders trace-log their own skip paths - DefaultExtractDecider: minimal access-level-only decider (analyzer fallback) - WithModifiersSyntax.passesAccessLevel(_:in:): shared helper, public overload takes ExtractedNominalType? so deciders don't need SPI access - JavaExtractDecider takes AccessLevelMode at init and now also handles the Java-specific operator skip (@JavaExport / @JavaClass-family rules unchanged) - Drop extractsOperators from SwiftExtractConfiguration + conformers; a language target that wants operators just omits the operator branch in its decider - SwiftAnalysisVisitor.shouldExtract becomes a one-line delegate; drop the operator switch from the function visitor - Wire accessLevel from config at the Java production call site (Swift2Java) and the two test helpers (TextAssertions, LoweringAssertions)
Adds a new structural `SwiftType` case for Swift's fixed-size InlineArray: indirect case inlineArray(count: Int?, element: SwiftType) Why a dedicated case rather than `.nominal` + a new `SugarName`: - The count is an `Int`, not a `SwiftType`, so it doesn't fit `genericArguments: [SwiftType]`. - Other structural shapes (`tuple`, `composite`) live as top-level cases. - Forcing every consumer to make an explicit decision is the point. `count` is optional so the wildcard form `[_ of T]` (in generic contexts), non-literal counts, and zero/negative counts can be parsed and surfaced as nil — downstream gates treat these as unsupported. Counts use `IntegerLiteralExprSyntax.representedLiteralValue`, which correctly handles radix prefixes (`0x`, `0b`, `0o`) and underscore separators. Description renders the sugar form (`[N of T]`), matching how `[T]`, `[K: V]`, and `T?` are printed. JExtract (FFM + JNI) generators are updated mechanically to route `.inlineArray` through their existing `unhandledType`/ `unsupportedSwiftType` paths — no Java-side semantic support in this change. Functions referencing `InlineArray` are skipped with a diagnostic, the same way other unhandled structural types are. Tests/SwiftExtractTests/InlineArrayTypeTests.swift covers the sugar form, explicit `InlineArray<N, T>`, underscore + hex counts, return positions, nested arrays, and description round-trip.
- Remove the parenthetical "(such as honoring Java's `@JavaExport`…)" clause from the type-level doc — that example belongs on `ExtractDecider` (where it already appears) and the type-level doc reads cleaner as a one-liner about analysis output. - Trim two analyze() overload docs that named specific downstream configuration sources by example. The hook description is enough on its own; the analyzer is meant to be language-neutral and the doc shouldn't pin to particular downstream callers.
Every language target needs to pick a per-decl extraction policy (access-level-only baseline vs. attribute-aware), so silently falling back to `DefaultExtractDecider` from the `SwiftAnalysisVisitor` was the wrong shape — it hides the choice and lets a downstream caller forget to supply one without compile-time pushback. - `SwiftAnalyzer.extractDecider` and the three constructor / static `analyze` entry points all take `any ExtractDecider` (no default). - `SwiftAnalysisVisitor.shouldExtract`'s `decider:` parameter is no longer optional; the internal fallback to `DefaultExtractDecider` is gone. - `SwiftExtractTests` adds a `Support/TestAnalyze.swift` helper (`analyze(sources:moduleName:config:sourceDependencies:)`) so the 21 existing call sites stay terse — they all want the access-level-only baseline anyway. Test-side migration is purely a rename (`SwiftAnalyzer.analyze` → `analyze`).
…AccessLevelMode The protocol requirement and swift-java's bridge property were two names for the same value. Aligning on `effectiveMinimumInputAccessLevelMode` (which already exists on `SwiftJavaConfigurationShared.Configuration`) lets `Configuration` conform to `SwiftExtractConfiguration` automatically, dropping the six-line passthrough property in `Configuration+SwiftExtract.swift`. - `SwiftExtractConfiguration.swiftExtractAccessLevel` → `effectiveMinimumInputAccessLevelMode` (protocol requirement + `DefaultSwiftExtractConfiguration` storage). - Drop the swift-java passthrough; the conformance now satisfies via the property already on `Configuration`. - Update the two call sites (`JavaExtractDecider`, `Tests/.../TestAnalyze.swift`).
Hash-comment languages reuse `CodePrinter` for their generated output but need source-location trailers, `printSeparator` banners, and echo-mode (`outputDirectory == "-"`) headers to start with `#` instead of `//`. Add a public `InlineCommentStyle` enum plus a `CodePrinter.inlineCommentStyle` instance flag. - Default is `.slashSlash`, so existing Swift / Java output is byte-identical. - The three internal hard-coded `//` fragments now use `inlineCommentStyle.rawValue`. - `PrinterTerminator.sloc`'s rawValue placeholder is irrelevant to output (the print path special-cases the trailer assembly), left unchanged. - Add a `CodePrintingTests` target with four tests covering the default `//`, `.hash` flip on `.sloc`, `.hash` flip on `printSeparator`, and `emitSourceLocations = false` interaction.
The 386a749 rename pass renamed types (`SwiftAnalyzer`, `ExtractedNominalType`, `extractedTypes`, …) but left the in-code identifiers and a few doc strings on the old vocabulary (`translator`, `imported`/`importedType`/`alreadyImported`). Sweep them now so the module reads consistently — the type and its in-code identifier finally agree. - `SwiftAnalysisVisitor.translator` field & `init(translator:)` parameter → `analyzer` / `init(analyzer:)`. - Every `translator.<member>` access in the visitor and analyzer becomes `analyzer.<member>`. - `Logger(label: "translator")` → `Logger(label: "analyzer")`. - `let imported = ExtractedFunc(…)` and friends → `let extracted`. - `importedType` / `importedNominal` / `alreadyImported` / `importedCase` locals → `extractedType` / `extractedNominal` / `alreadyExtracted` / `extractedCase`. - Companion doc-comment cleanups ("imported nominal type representation" → "extracted …", "imported representation" → "extracted …", "Record imported method" → "Record extracted …"). Module-import vocabulary (`importedModules`, `importedModuleStubs`, `importingModules`, `importedModule`) is left alone — those refer to literal Swift `import` statements, not the analyzer's extraction step.
…ractDecider
The 'per-decl extraction policy belongs to each ExtractDecider' refactor
left this Bool on SwiftExtractConfiguration as a side-channel that the
visitor read *after* the decider had already approved the decl:
guard node.shouldExtract(... decider: analyzer.extractDecider) else { return }
if typeContext.swiftNominal.isGeneric && !typeContext.isSpecialization
&& !config.extractsGenericTypeInitializers { return }
That contradicts the invariant the refactor was about. Move the rule
into JavaExtractDecider — the decider already receives 'parent:
ExtractedNominalType?' which exposes 'swiftNominal.isGeneric' and
'isSpecialization', so the check fits naturally there:
if let parent,
decl.is(InitializerDeclSyntax.self),
parent.swiftNominal.isGeneric,
!parent.isSpecialization
{
log.trace("Skip ...: initializer of an unspecialized generic type")
return false
}
Placed before the @JavaExport force-include path so an explicit export
on a generic init still gets dropped — matches today's flag-based
behavior, where the visitor's gate runs after the decider's @JavaExport
short-circuit and drops it anyway.
Other targets that DO want generic-base initializers (swift-python's
specialization pipeline clones them onto each typealias) get them by
default now: their decider doesn't add the skip rule, so the visitor
sees the init and records it.
Removed:
- SwiftAnalysisVisitor.swift gate at lines 327-329
- SwiftExtractConfiguration.extractsGenericTypeInitializers protocol
member + DefaultSwiftExtractConfiguration stored property + init param
- JExtractSwiftLib/Configuration+SwiftExtract override
Tests:
- AnalysisResultTests: rewrote 'unspecializedGenericInitializersAreSkippedByDefault'
+ 'extractsGenericTypeInitializersKeepsBaseInitializers' into a single
'unspecializedGenericInitializersFlowThroughByDefault' that asserts the
neutral analyzer + DefaultAccessLevelExtractDecider keeps base inits.
- SpecializationTests: added 'javaDeciderDropsBaseGenericInitializers'
locking in JavaExtractDecider's new rule.
…tExtractDecider -> DefaultAccessLevelExtractDecider The 'output' qualifier on ExtractedNominalType.effectiveOutputName / effectiveOutputSimpleName conflated two roles: the Swift-side registration key in the analyzer's type table (which is purely language-neutral — 'FishBox' for a 'typealias FishBox = Box<Fish>', 'Box' for the base) and the downstream language-output-facing class name (which lives on the code generator). Rename the neutral one to .effectiveTypeName, drop the now-redundant .effectiveOutputSimpleName in favor of computing the simple name on-site (specializedTypeName ?? swiftNominal.name) where it's actually used. JExtractSwiftLib's Java-facing aliases get explicit doc comments and the .effectiveJavaSimpleName / .effectiveJavaName accessors are spelled out instead of bouncing off the (now removed) neutral helpers. Rename DefaultExtractDecider -> DefaultAccessLevelExtractDecider so the role is in the name: it's the access-level-only baseline, not a catch-all default. SwiftAnalyzer's doc comment, TestAnalyze, and the ExtractDecider doc comment all updated to match. Trim a couple of doc comments that just paraphrased the @test description or the function name.
…nresolvedTypePlaceholder flag
The 'is this a synthetic placeholder?' bit was being smuggled through the
type's moduleName as the sentinel string '__SwiftExtractSynthesized'. Two
problems with that: (1) the marker is a string sentinel embedded in
semantic data, so anything that prints, logs, or compares moduleName has
to know to special-case it; (2) it conflates two unrelated dimensions —
'what module declared this type' (real Swift semantics) vs. 'did
SwiftExtract synthesize this stand-in because the symbol table couldn't
resolve the name' (analyzer bookkeeping).
Replace with an explicit Bool on SwiftNominalTypeDeclaration:
/// True when this declaration is a placeholder synthesized by
/// SwiftSyntheticTypes.unresolvedNominal(_:) because the symbol
/// table couldn't resolve the name.
///
/// Exists to support **lazy specializations** — code generators
/// that resolve names later than analysis time…
public let isUnresolvedTypePlaceholder: Bool
The doc comment carries the long-form motivation (the canonical
associated-type-in-protocol-requirement example, plus pre-specialization
generic parameters and externally-bridged simple-name types). The
SwiftType-level computed property is a thin pass-through:
extension SwiftType {
public var isUnresolvedTypePlaceholder: Bool {
asNominalTypeDeclaration?.isUnresolvedTypePlaceholder ?? false
}
}
SwiftSyntheticTypes.unresolvedNominal stamps the synthetic decl with
isUnresolvedTypePlaceholder: true and an empty moduleName — the honest
answer to 'what module declared this?'. The moduleName: parameter on
unresolvedNominal goes away (no caller passed a non-default value).
The public static syntheticModuleName constant goes away with it.
Tests/SwiftExtractTests/SwiftSyntheticTypesTests.swift covers the
round-trip: unresolvedNominal('Element') has isUnresolvedTypePlaceholder
== true and renders as 'Element' (no module-name leak); a real
source-derived nominal has isUnresolvedTypePlaceholder == false.
Lets a downstream caller spell its choice at construction time instead
of constructing then mutating:
CodePrinter(inlineCommentStyle: .hash)
Default stays .slashSlash so existing call sites are unchanged.
…p bridge
LogLevel was duplicated as Logger.Level in SwiftExtract and as a
parallel LogLevel in SwiftJavaConfigurationShared, glued together by
two switch statements in JExtractSwiftLib/Configuration+SwiftExtract.swift.
Same-shape enum, two case-by-case bridges — exactly the conflation
AccessLevelMode already retired by living in
SwiftExtractConfigurationShared. Apply the same pattern to LogLevel.
- Sources/SwiftExtractConfigurationShared/LogLevel.swift (new) holds
the canonical enum with Codable, ExpressibleByStringLiteral,
Comparable conformances. The plugin symlink at
Plugins/PluginsShared/SwiftExtractConfigurationShared/ picks it up
automatically alongside AccessLevelMode.swift.
- SwiftExtract/Logger.swift drops the nested Logger.Level enum and
uses LogLevel via @_exported import — the analyzer's API surface
is now Logger(label:logLevel: LogLevel).
- SwiftJavaConfigurationShared/Configuration.swift drops its own
LogLevel enum + Codable extensions; the @_exported import of
SwiftExtractConfigurationShared brings the shared one in.
- SwiftExtractConfiguration's protocol requirement renames from
swiftExtractLogLevel: Logger.Level? to logLevel: LogLevel? — the
swiftExtract-prefix existed to dodge a name collision against
Configuration.logLevel that no longer exists (both sides reference
the same type now), so Configuration: SwiftExtractConfiguration is
satisfied by the stored property without any bridge code.
- JExtractSwiftLib/Configuration+SwiftExtract.swift collapses to an
empty conformance declaration (`extension Configuration: SwiftExtractConfiguration {}`).
The two switch statements (LogLevel <-> Logger.Level) are gone.
- SwiftJavaTool: CommonOptions.logLevel and the SwiftJavaBaseAsyncParsableCommand
protocol's logLevel both type-annotate LogLevel directly; the
override-from-CLI line collapses from
`config.logLevel = LogLevel(command.logLevel)` to
`config.logLevel = command.logLevel`.
- Logger+ArgumentParser.swift's ExpressibleByArgument conformance moves
from Logger.Level to LogLevel.
- Tests: FunctionDescriptorImportTests' two helper-default annotations
rename Logger.Level -> LogLevel.
Net -118 lines (one duplicate enum + two switch bridges deleted), and
the Configuration -> SwiftExtractConfiguration conformance is now an
empty declaration instead of two case-by-case mappings.
…ntax JExtractSwiftLib needs to read the underlying syntax attributes to recognize `@JavaClass`/`@JavaInterface`/`@JavaExport` etc. on a SwiftNominalTypeDeclaration, which forced a `@_spi(Testing) import SwiftExtract` in JExtractSwiftLib/SwiftSyntax+Java.swift. The SPI gating wasn't really protecting anything — once a downstream language target wants to inspect attributes on a nominal, the test-only label becomes a lie. - NominalTypeDeclSyntaxNode typealias and SwiftNominalTypeDeclaration.syntax are now plain `public` (also fixed the typo `/////` -> `///` on the typealias's leading doc comment). - The synthesizing init() keeps its @_spi(Testing) — building a fake nominal from outside SwiftExtract genuinely is a test-only need. - JExtractSwiftLib/SwiftSyntax+Java.swift: `@_spi(Testing) import SwiftExtract` -> `import SwiftExtract`.
…nown names into a constant The attribute these wrappers come from belong to JavaKit (the Java -> Swift wrapping layer), so 'JavaKit' captures the relationship better than the more generic 'SwiftJava'. While renaming, lift the inline list of attribute names into a top-level constant so the names have one source of truth and the predicate body becomes a single `contains` call instead of a multi-case switch. - New top-level `KnownJavaKitMacroNames: [String]` lists the seven attribute names (`JavaClass`, `JavaInterface`, `JavaField`, `JavaStaticField`, `JavaMethod`, `JavaStaticMethod`, `JavaImplementation`). - `AttributeListSyntax.Element.isSwiftJavaMacro` -> `isJavaKitMacro`, body collapses to `KnownJavaKitMacroNames.contains(attrName)`. - `SwiftNominalType.isSwiftJavaWrapper` updates its only call site (`\.isSwiftJavaMacro` -> `\.isJavaKitMacro`); the property name stays as-is — it describes the type's role (a Swift wrapper for a Java class), not the macro name. - `JavaExtractDecider`'s `isSwiftJavaMacro` callsite updated.
The call site reads as English now:
mod.isAtLeast(accessLevel, in: parent)
Two overloads (the public ExtractedNominalType-parent variant and the
package NominalTypeDeclSyntaxNode-parent variant) both renamed; both
ExtractDecider implementations (DefaultAccessLevelExtractDecider,
JavaExtractDecider) updated.
The protocol was passing the analyzer's logger into every shouldExtract call. That hid two things: (1) the decider couldn't be constructed and asked outside the analyzer's context (the caller had no logger to give it), and (2) the decider's trace messages all came out under the analyzer's logger label, masking which decider produced them. Make each decider own its logger: - DefaultAccessLevelExtractDecider's init takes `logLevel: LogLevel = .info` and stores a Logger labelled `DefaultAccessLevelExtractDecider`. - JavaExtractDecider's init takes the same; `makeSwiftJavaAnalyzer` threads `config.logLevel ?? .info` in. - ExtractDecider.shouldExtract drops `log: Logger` parameter; the visitor's wrapper drops it too. - All six call sites (`SwiftAnalysisVisitor` x4, `SwiftAnalyzer` x2) drop `log: log`. Doc comment updated to spell out the new contract: 'implementations should emit a .trace line on every skip path (using their own logger)'.
The String dictionary key on extractedTypes hid what the key actually is. SwiftExtract already exposes the typealias `SwiftTypeName = String` in SourceDependencies.swift; use it on the four [String: ExtractedNominalType] occurrences so the role of the key reads from the type signature. - SwiftAnalyzer.extractedTypes - AnalysisResult.extractedTypes (+ initializer) - ExtractedNominalType.conformsTo(_:in:)'s parameter - JNISwift2JavaGenerator+JavaTranslation's local extractedTypes ref
…x param order
The analyze API carried two overloads on both the instance and static
sides — one with the hook required, one taking no hook and trampolining
to the first with an empty closure. Default the hook to `{ _ in }`
and drop the trampolining overloads.
Also fix the static analyze's parameter order: the rule is 'defaulted
parameters follow non-defaulted', and `extractDecider:` (required)
was sandwiched between `config:` and `sourceDependencies:` (both
defaulted). Move `extractDecider:` up so all three required
parameters lead. TestAnalyze's call site reorders to match (kwargs
were already used so no signature change leaks beyond the file).
…tion.swift `visitFoundationDeclsIfNeeded` is a self-contained ~65-line block that overlays Foundation/FoundationEssentials known types when the analyzed code references any of them. It belongs next to the rest of the analyzer's Foundation knowledge but doesn't need to live in the main `SwiftAnalyzer.swift` file. Move it into a sibling `SwiftAnalyzer+Foundation.swift` (still an extension on `SwiftAnalyzer`). Visibility goes from `private` to `internal` (no modifier) since its caller `analyze(...)` lives in the original file. The `isUsing` helper it depends on stays where it is and remains internal.
…edTypeReferences Reads more naturally as a config flag — `allow` is the verb you ask when toggling a permission, `permits` is the verb you'd describe one in the third person. Renames the protocol requirement, both default and stored implementations on `SwiftExtractConfiguration`/`Default*`, the corresponding mirror on `SwiftTypeLookupContext`, and the two read sites in `SwiftType.init` and `SwiftAnalyzer.prepareForTranslation`.
Companion to 376062f4 (which dropped the SPI on the .syntax stored property and on the typealias). The init's SPI gate only restricted external callers; all four in-module call sites (ExtractedDecls, SwiftSyntheticTypes, SwiftTypeLookupContext, SwiftParsedModuleSymbolTableBuilder) bypass it because they're in the same module. The bookkeeping bit `isUnresolvedTypePlaceholder` on the init means external code that wants to mint synthetic nominals can do so directly (matching the API direction we've been moving in). Consistent with the rest of the type's surface being plain `public`.
Collaborator
Author
|
I deeply reviewed all the files and validated against a few consumers, I think this is good enough first stab at the split. |
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.
This is an initial PR to extract a lot of the analysis code into an reusable
SwiftExtractmodule. The module has no source compatibility guarantees at this point. The intent is to facilitate other language source generators that can reuse the same analysis core which is "the tricky bit".This fully rebases swift-java onto this shared infrastructure, without loss of features; and even gains a few along the way, like being better prepared for lazy specialization with typealiases.
This is a massive "move stuff around" so reviewing everyting might be hard but the long term benefit will be massive.