mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-28 06:22:22 +00:00
fix(kotlin): detect default parameter arity (#2034)
* fix(kotlin): detect default parameter arity * test(kotlin): rebaseline optional arity captures * test(kotlin): cover default parameter boundaries
This commit is contained in:
parent
479b8146b8
commit
2ce95d6e42
4 changed files with 81 additions and 13 deletions
|
|
@ -74,9 +74,9 @@
|
|||
"_rebaselined": "#1956 synth-widening: + javascript-qualified-base fixture; synthesizeJsInheritanceReferences now handles a member_expression base (class S extends ns.Base -> Base), matching the #1940 legacy leg + the TS terminalTsTypeNameNode property_identifier case, at parity. Linear (~1.05). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged."
|
||||
},
|
||||
"kotlin": {
|
||||
"fingerprint": "9b212eca24959cc1705933df213f4ec739c5c1b4580c7929347d37bf4d72ba8a",
|
||||
"fingerprint": "a16400622892183581b8f5f8fa01f07842d19b8cf49ed021df52bd17009d749f",
|
||||
"scaling_budget": 1.5,
|
||||
"_added": "#1951: bench coverage added (was ungated); scale source heritage-bearing (: Base()); js/kotlin O(n^2) findNodeAtRange-per-match fixed to threaded captured node, now linear.",
|
||||
"_rebaselined": "#1956 synth-widening: + kotlin-qualified-base fixture; synthesizeKotlinInheritanceReferences now handles the explicit_delegation form (class F : Iface by d -> Iface), matching the #1940 legacy leg, at parity. Linear (~0.87). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged."
|
||||
"_rebaselined": "#1956 synth-widening: + kotlin-qualified-base fixture; synthesizeKotlinInheritanceReferences now handles the explicit_delegation form (class F : Iface by d -> Iface), matching the #1940 legacy leg, at parity. Linear (~0.87). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged. | #1930 F45: default parameters now emit optional-arity metadata; capture fingerprint changes, scaling remains linear."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,16 @@ export const javaMethodConfig: MethodExtractionConfig = {
|
|||
|
||||
const KOTLIN_VIS = new Set<MethodVisibility>(['public', 'private', 'protected', 'internal']);
|
||||
|
||||
function kotlinParameterHasDefaultValue(param: SyntaxNode): boolean {
|
||||
// tree-sitter-kotlin inlines `_function_value_parameter`, so `=` and its
|
||||
// expression are siblings of `parameter`, not children of the parameter.
|
||||
for (let sibling = param.nextSibling; sibling !== null; sibling = sibling.nextSibling) {
|
||||
if (sibling.type === ',' || sibling.type === ')') return false;
|
||||
if (sibling.type === '=') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractKotlinParameters(node: SyntaxNode): ParameterInfo[] {
|
||||
const params: ParameterInfo[] = [];
|
||||
// Kotlin: function_declaration > function_value_parameters > parameter
|
||||
|
|
@ -199,7 +209,6 @@ function extractKotlinParameters(node: SyntaxNode): ParameterInfo[] {
|
|||
let paramName: string | undefined;
|
||||
let paramType: string | null = null;
|
||||
let paramRawType: string | null = null;
|
||||
let hasDefault = false;
|
||||
const isVariadic = nextIsVariadic;
|
||||
nextIsVariadic = false;
|
||||
|
||||
|
|
@ -218,21 +227,12 @@ function extractKotlinParameters(node: SyntaxNode): ParameterInfo[] {
|
|||
}
|
||||
}
|
||||
|
||||
// Check for default value: `= expr`
|
||||
for (let k = 0; k < param.childCount; k++) {
|
||||
const c = param.child(k);
|
||||
if (c && c.text === '=') {
|
||||
hasDefault = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (paramName) {
|
||||
params.push({
|
||||
name: paramName,
|
||||
type: paramType,
|
||||
rawType: paramRawType,
|
||||
isOptional: hasDefault,
|
||||
isOptional: kotlinParameterHasDefaultValue(param),
|
||||
isVariadic: isVariadic,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,26 @@ function captureTexts(source: string): Array<Record<string, string>> {
|
|||
}
|
||||
|
||||
describe('Kotlin scope captures', () => {
|
||||
it('emits required arity equal to total arity when no parameters have defaults', () => {
|
||||
const captures = captureTexts(`
|
||||
fun greet(name: String, greeting: String) {}
|
||||
`);
|
||||
const declaration = captures.find((match) => match['@declaration.function'] !== undefined);
|
||||
|
||||
expect(declaration?.['@declaration.parameter-count']).toBe('2');
|
||||
expect(declaration?.['@declaration.required-parameter-count']).toBe('2');
|
||||
});
|
||||
|
||||
it('emits required arity excluding default parameters', () => {
|
||||
const captures = captureTexts(`
|
||||
fun greet(name: String, greeting: String = "Hello", punctuation: String = "!") {}
|
||||
`);
|
||||
const declaration = captures.find((match) => match['@declaration.function'] !== undefined);
|
||||
|
||||
expect(declaration?.['@declaration.parameter-count']).toBe('3');
|
||||
expect(declaration?.['@declaration.required-parameter-count']).toBe('1');
|
||||
});
|
||||
|
||||
it('counts trailing lambda call suffixes as call arguments', () => {
|
||||
const captures = captureTexts(`
|
||||
fun run(items: List<String>) {
|
||||
|
|
|
|||
|
|
@ -504,6 +504,54 @@ describeKotlin('Kotlin MethodExtractor', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('default parameters', () => {
|
||||
it('keeps all required parameters non-optional', () => {
|
||||
const tree = parseKotlin(`
|
||||
class Greeter {
|
||||
fun greet(name: String, greeting: String) { }
|
||||
}
|
||||
`);
|
||||
const classNode = tree.rootNode.child(0)!;
|
||||
const result = extractor.extract(classNode, kotlinCtx);
|
||||
|
||||
expect(result!.methods[0].parameters.map((parameter) => parameter.isOptional)).toEqual([
|
||||
false,
|
||||
false,
|
||||
]);
|
||||
});
|
||||
|
||||
it('marks parameters with default expressions as optional', () => {
|
||||
const tree = parseKotlin(`
|
||||
class Greeter {
|
||||
fun greet(name: String, greeting: String = "Hello", punctuation: String = "!") { }
|
||||
}
|
||||
`);
|
||||
const classNode = tree.rootNode.child(0)!;
|
||||
const result = extractor.extract(classNode, kotlinCtx);
|
||||
|
||||
expect(result!.methods[0].parameters.map((parameter) => parameter.isOptional)).toEqual([
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves a required parameter after a defaulted parameter', () => {
|
||||
const tree = parseKotlin(`
|
||||
class Greeter {
|
||||
fun greet(greeting: String = "Hello", name: String) { }
|
||||
}
|
||||
`);
|
||||
const classNode = tree.rootNode.child(0)!;
|
||||
const result = extractor.extract(classNode, kotlinCtx);
|
||||
|
||||
expect(result!.methods[0].parameters.map((parameter) => parameter.isOptional)).toEqual([
|
||||
true,
|
||||
false,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extension functions', () => {
|
||||
it('extracts receiverType for extension functions', () => {
|
||||
const tree = parseKotlin(`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue