Files
codegraph/__tests__/swift-constructed-receiver.test.ts
Colby Mchenry 4c381aedbd fix(swift): a call on a constructed value is that type's member (#2203)
A chained Swift call accepted any same-named member unless its name was a
standard-library one: vapor's `JSONDecoder().decode(...)` and even its own
`URLQueryDecoder().decode(...)` went to `ClientRequest._URLQueryContainer
.decode`, IceCubes' `JSONEncoder().encode(self)` to a filter's `encode`,
realm's `Realm().create(...)` to a test object's `create`.

When the link before the call is a constructor - `TypeName(...)` with flat
arguments - the member must belong to that type or a supertype / protocol
the project gives it; an outside type (JSONDecoder) keeps only the
project's extensions of it. A capitalized C function
(`RLMObjectBaseObjectSchema(obj)!`) constructs nothing and is left alone.

vapor -63 +62 (`URLQueryDecoder::decode`, `URLEncodedFormParser::parse`),
IceCubesApp -23 +1, realm-swift -19 +19 (`Realm::create`), Alamofire -1;
Kingfisher unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:03:23 +00:00

63 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* A Swift call on a just-constructed value — `URLQueryDecoder().decode(…)`,
* `JSONDecoder().decode(…)` — is a member of the type constructed (or of what
* it inherits), never some other type's method of that name: vapor's
* `JSONDecoder().decode` went to a request's private
* `_URLQueryContainer.decode`, and so did its own decoders' calls.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-swift-constructed-'));
const files: Record<string, string> = {
'Sources/App/URLQueryDecoder.swift': `struct URLQueryDecoder {
func decode<D>(_ type: D.Type) -> Int {
return 0
}
}
`,
'Sources/App/Request.swift': `struct Request {
struct _Container {
func decode<D>(_ type: D.Type) -> Int {
return 1
}
}
}
`,
'Sources/App/Use.swift': `import Foundation
func run(data: Data) throws -> Int {
let a = URLQueryDecoder().decode(Int.self)
let b = try JSONDecoder().decode(Int.self, from: data)
return a + b
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('Swift calls on a constructed value', () => {
it('reach the constructed type’s member, and no other type’s', () => {
const run = cg.getNodesInFile('Sources/App/Use.swift').find((n) => n.name === 'run')!;
const decodes = cg.getOutgoingEdges(run.id).filter((e) => e.kind === 'calls')
.map((e) => cg.getNode(e.target)!).filter((t) => t.name === 'decode').map((t) => t.qualifiedName);
expect(decodes).toEqual(['URLQueryDecoder::decode']);
});
});