From 84f4eb42e1ae7e9f2af89c8299d023b4b727e3b6 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:06:16 +0800 Subject: [PATCH] fix(native): preserve unknown Swift extension contexts --- src/domain/swiftSymbolClassification.ts | 9 ++++++++- src/domain/symbolAnalysis.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/domain/swiftSymbolClassification.ts b/src/domain/swiftSymbolClassification.ts index 90aa8783f2..afff0a3829 100644 --- a/src/domain/swiftSymbolClassification.ts +++ b/src/domain/swiftSymbolClassification.ts @@ -42,7 +42,14 @@ export const classifySwiftSymbol = ( // A zero-prefixed identifier uses word substitutions or Punycode. We // cannot tell whether it continues a nested type or names a member, so // retaining the outer category would be a guess. - if (mangling.charAt(offset) === "0") category = null; + if ( + mangling.charAt(offset) === "0" || + // A substituted module can introduce a constrained extension after + // the nominal type. This parser does not decode that context, so it + // cannot retain the preceding nominal category as the member's owner. + mangling.startsWith("AA", offset) + ) + category = null; break; } const kind = mangling.charAt(end); diff --git a/src/domain/symbolAnalysis.test.ts b/src/domain/symbolAnalysis.test.ts index dfca4c238c..fa66e668ad 100644 --- a/src/domain/symbolAnalysis.test.ts +++ b/src/domain/symbolAnalysis.test.ts @@ -25,6 +25,30 @@ describe("modern Swift category evidence", () => { }); }); + it("keeps undecoded substituted extension contexts unclassified", () => { + const names = [ + "_$s1M7WrapperVAASQRzlE4sameySbxF", + "_$s1M3BoxVAARi_zrlE1vxvM", + ]; + const result = categorizeSwiftTypes( + names.map((name, index) => ({ address: String(index), name })), + ); + expect(result.categories.structs.count).toBe(0); + expect(result.categories.other.count).toBe(2); + expect(result.unclassified.map(({ name }) => name)).toEqual(names); + const controls = [ + ["_$s1M7WrapperV1vxvg", "structs"], + ["_$s1M7WrapperVMa", "structs"], + ["_$s1M7WrapperVACyxGxcfC", "structs"], + ["_$s10Foundation4DataV1ME10countTwiceSiyF", "extensions"], + ] as const; + for (const [name, category] of controls) + expect( + categorizeSwiftTypes([{ address: "0x1", name }]).categories[category] + .count, + ).toBe(1); + }); + it("retains unresolved Swift observations under category filters", () => { const names = [ "$s4main13swiftIndirectySiAA7Scoring_p_SitF",