mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
parent
3d09c85ee3
commit
b20c8b6ef2
5 changed files with 94 additions and 4 deletions
|
|
@ -121,6 +121,20 @@ export function recordPythonSubtypeMethodShape(
|
|||
fnNode: SyntaxNode,
|
||||
mapLine?: LineMapper,
|
||||
): void {
|
||||
// An unknown decorator may replace the function or mark it abstract.
|
||||
// Positional shape alone cannot prove a concrete subtype dispatch target.
|
||||
// The two built-in descriptor decorators are handled by receiver binding.
|
||||
const wrapper = fnNode.parent;
|
||||
if (
|
||||
wrapper?.type === 'decorated_definition' &&
|
||||
wrapper.namedChildren.some((child) => {
|
||||
if (child.type !== 'decorator') return false;
|
||||
const name = child.firstNamedChild?.text;
|
||||
return name !== 'staticmethod' && name !== 'classmethod';
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const capacity = positionalCapacity(fnNode);
|
||||
if (capacity === undefined) return;
|
||||
const [line, column] = nodePosition(fnNode, mapLine);
|
||||
|
|
|
|||
|
|
@ -800,7 +800,10 @@ import { copyV8CacheIfPresent, tryLoadV8Cache, writeV8CacheFile } from './v8-sid
|
|||
// v117 (#3390 private-only successor): simple-positional call entries now carry
|
||||
// their count privately, while ordinary Python references no longer receive
|
||||
// synthetic arity. Warm v116 ParsedFiles have neither equivalent fact.
|
||||
const SCHEMA_BUMP = 117;
|
||||
// v118 (#3398): decorated Python methods with unproven decorator identity no
|
||||
// longer publish subtype positional capacity. Warm v117 side-channel snapshots
|
||||
// would retain that capacity and could emit a false concrete call target.
|
||||
const SCHEMA_BUMP = 118;
|
||||
const GITNEXUS_PKG_VERSION = (() => {
|
||||
try {
|
||||
// package.json sits at gitnexus/package.json — two levels up from
|
||||
|
|
|
|||
|
|
@ -1349,6 +1349,43 @@ describe('Python mixin self-dispatch', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Python aliased abstract subtype method', () => {
|
||||
it('does not publish an abstract declaration as a concrete self-dispatch target', async () => {
|
||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-python-abstract-alias-'));
|
||||
try {
|
||||
writeFixtureRepo(repoDir, {
|
||||
'worker.py': [
|
||||
'from abc import ABC, abstractmethod as am',
|
||||
'class Mixin:',
|
||||
' def dispatch(self):',
|
||||
' return self.hook()',
|
||||
'class AbstractWorker(Mixin, ABC):',
|
||||
' @am',
|
||||
' def hook(self):',
|
||||
' return 1',
|
||||
].join('\n'),
|
||||
});
|
||||
const result = await runPipelineFromRepo(repoDir, () => {});
|
||||
expect(
|
||||
getRelationships(result, 'CALLS').filter(
|
||||
(call) => call.source === 'dispatch' && call.target === 'hook',
|
||||
),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
getResolutionOutcomes(result).some(
|
||||
(outcome) =>
|
||||
outcome.kind === 'suppressed' &&
|
||||
outcome.filePath === 'worker.py' &&
|
||||
outcome.name === 'hook' &&
|
||||
outcome.reason === 'receiver-unresolved',
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Incomplete Python inheritance must not invent an MRO binding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -297,8 +297,8 @@ describe('PARSE_CACHE_VERSION', () => {
|
|||
// Moved 114 -> 115 for #3390: statically known Python call arity.
|
||||
// Moved 115 -> 116 for #3390's Python subtype-dispatch shape side-channel.
|
||||
// Moved 116 -> 117 for #3390's private positional-count side-channel.
|
||||
it('pins SCHEMA_BUMP to 117 so concurrent bumps cannot silently collide (#2766, #3015, #3088, #2885, #3128, #2865, #3130, #1432, #3161, #3179, #3219, #3190, #3253, #3273, #3339, #3354, #3371, #2965, #3390)', () => {
|
||||
expect(Number(PARSE_CACHE_VERSION.split('+', 1)[0])).toBe(117);
|
||||
it('pins SCHEMA_BUMP to 118 so concurrent bumps cannot silently collide (#2766, #3015, #3088, #2885, #3128, #2865, #3130, #1432, #3161, #3179, #3219, #3190, #3253, #3273, #3339, #3354, #3371, #2965, #3390, #3398)', () => {
|
||||
expect(Number(PARSE_CACHE_VERSION.split('+', 1)[0])).toBe(118);
|
||||
expect(PARSE_CACHE_BUCKET_COUNT).toBe(128);
|
||||
// The PREVIOUS version must fail the reuse gate, not merely differ from the
|
||||
// current one — a hardcoded number outside the conflict hunk rebases cleanly
|
||||
|
|
@ -307,7 +307,7 @@ describe('PARSE_CACHE_VERSION', () => {
|
|||
for (const taken of [
|
||||
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
|
||||
82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
|
||||
104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
|
||||
104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
|
||||
]) {
|
||||
expect(Number(PARSE_CACHE_VERSION.split('+', 1)[0])).not.toBe(taken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,4 +138,40 @@ describe('Python missing-member subtype argument shapes', () => {
|
|||
simplePositionalCalls: [[21, 0, 1]],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not prove a decorated subtype target when the decorator identity is unknown', () => {
|
||||
emitPythonScopeCaptures(callerSource, 'caller.py');
|
||||
emitPythonScopeCaptures(
|
||||
[
|
||||
'from abc import abstractmethod as am',
|
||||
'class AbstractWorker:',
|
||||
' @am',
|
||||
' def target(self, value):',
|
||||
' return value',
|
||||
'class StaticWorker:',
|
||||
' @staticmethod',
|
||||
' def target(value):',
|
||||
' return value',
|
||||
'class ClassWorker:',
|
||||
' @classmethod',
|
||||
' def target(cls, value):',
|
||||
' return value',
|
||||
].join('\n'),
|
||||
'targets.py',
|
||||
);
|
||||
|
||||
expect(
|
||||
pythonMissingReceiverSubtypeCandidateCompatibility('caller.py', positionalSite, candidate(4)),
|
||||
).toBe('unknown');
|
||||
expect(
|
||||
pythonMissingReceiverSubtypeCandidateCompatibility('caller.py', positionalSite, candidate(8)),
|
||||
).toBe('compatible');
|
||||
expect(
|
||||
pythonMissingReceiverSubtypeCandidateCompatibility(
|
||||
'caller.py',
|
||||
positionalSite,
|
||||
candidate(12),
|
||||
),
|
||||
).toBe('compatible');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue