Merge branch 'main' into feat/nuxt-auto-imports

This commit is contained in:
Gergő Magyar 2026-06-04 19:34:25 +01:00 • committed by GitHub
commit 28970e6d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 15 deletions

View file

@ -23,8 +23,8 @@
"_note": "#1975: + cpp-out-of-line-class fixture, fixture_count 263->265. #1990: + cpp-adl-ns-plus-hidden-friend-same-name fixture (ADL hidden-friend + namespace-callable merge parity test). Pure fixture-corpus drift \u2014 no scope-extractor change; existing fixtures' captures byte-identical. fixture_count 265->267. #1995: + cpp-union-nested-tail-collision and cpp-anon-ns-tail-collision fixtures \u2014 pure fixture-corpus drift; fixture_count 270->272, fingerprint 538e8be->d63ded6. #1993: + cpp-cross-namespace-same-tail fixture \u2014 pure fixture-corpus drift; fixture_count 272->273, fingerprint d63ded6->6d6207ae."
},
"csharp": {
"_rebaselined": "#1956 synth-widening: + csharp-qualified-base fixture; the synth now walks record_declaration + struct_declaration base_lists and handles alias_qualified_name (matching the #1940 legacy leg), so record/struct heritage now emits. csharp-record-base gains a record inherits capture. (record->record SAME-namespace EXTENDS is a separate registry resolution gap, tracked as follow-up.) Linear (~1.00). (Earlier #1956: heritage-bearing scale source.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.",
"fingerprint": "7e8845040540ae69ef564ebf597305ade31e4480cee1dc910ea0fcfc26794910",
"_rebaselined": "#1956 synth-widening: + csharp-qualified-base fixture; the synth now walks record_declaration + struct_declaration base_lists and handles alias_qualified_name (matching the #1940 legacy leg), so record/struct heritage now emits. csharp-record-base gains a record inherits capture. (record->record SAME-namespace EXTENDS is a separate registry resolution gap, tracked as follow-up.) Linear (~1.00). (Earlier #1956: heritage-bearing scale source.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged. | #1924 F16: record primary-constructor base bindings now exclude constructor arguments; capture fingerprint changes, scaling remains linear. | #2036 review follow-up: csharp-record-base now exercises primary-constructor base dispatch end to end; +2 capture groups, scaling remains linear.",
"fingerprint": "701b4274643a9a5ee03a71e9fc0dede28a89524eedb976affc48df72bdd4adcc",
"scaling_budget": 1.5
},
"rust": {

View file

@ -49,18 +49,22 @@ function typeName(typeNode: SyntaxNode): string | null {
return typeNode.childForFieldName('name')?.text ?? null;
}
/** First entry in the type's `base_list`, read as raw text. C# allows
* generic and qualified bases (`Foo<T>`, `N.M.Base`); we keep the raw
* form so downstream interpret layer can strip generics/qualifiers
* the same way as other type-binding captures. Returns null when the
* type has no base (or an empty base_list). */
/** First entry in the type's `base_list`, read as raw type text. C#
* allows generic and qualified bases (`Foo<T>`, `N.M.Base`); we keep
* that form so downstream interpretation can strip generics and
* qualifiers consistently. A record primary-constructor base wraps
* its type and constructor arguments in `primary_constructor_base_type`,
* so read its `type` field instead of leaking `(args)` into the binding.
* Returns null when the type has no base (or an empty base_list). */
function firstBaseText(typeNode: SyntaxNode): string | null {
for (let i = 0; i < typeNode.namedChildCount; i++) {
const child = typeNode.namedChild(i);
if (child === null || child.type !== 'base_list') continue;
const firstBase = child.namedChild(0);
if (firstBase === null) return null;
return firstBase.text;
return firstBase.type === 'primary_constructor_base_type'
? (firstBase.childForFieldName('type')?.text ?? firstBase.text)
: firstBase.text;
}
return null;
}

View file

@ -489,7 +489,7 @@
},
"csharp-qualified-base/src/Shapes.cs": {
"captureGroups": 38,
"digest": "9f7730c2579cff867f929efc8ad9e314f847b7e020174282c7c104339ad5b610"
"digest": "eb485282a87a35dac553701e6560d26b4e01f585fa36ded4576bbce00c8c163a"
},
"csharp-qualified-types/Data/User.cs": {
"captureGroups": 7,
@ -512,12 +512,12 @@
"digest": "4f0c929c82e56f41e5b3b4c0aefee2121d7a307ef24535ac998fd7041d0e025b"
},
"csharp-record-base/src/Models/BaseEntity.cs": {
"captureGroups": 7,
"digest": "e7da2e190dad718eeaa22dad011740fd2086eb2f86331d887e47cd9c5092003c"
"captureGroups": 8,
"digest": "1ed96b53bfd3a0ed13a7931e82b0b47f4c4acfc23d23f04d4053c7ad994cf9f9"
},
"csharp-record-base/src/Models/UserRecord.cs": {
"captureGroups": 10,
"digest": "e6276cd40438125f468adb2f193786a6e888032a3980d0d7ac586d355c40534c"
"captureGroups": 11,
"digest": "416980acf3f3df44c96c375de8cd683031e053b3d82cf1a0ea5ae66b056438b6"
},
"csharp-recursive-pattern/Models/Repo.cs": {
"captureGroups": 8,

View file

@ -1,6 +1,6 @@
namespace Models;
public record BaseEntity
public record BaseEntity(int EntityId)
{
public virtual bool Save() { return true; }
}

View file

@ -1,6 +1,6 @@
namespace Models;
public record UserRecord : BaseEntity
public record UserRecord(int Id) : BaseEntity(Id)
{
public override bool Save()
{

View file

@ -308,6 +308,61 @@ describe('emitCsharpScopeCaptures — receiver-binding synthesis (`this` / `base
expect(baseMatch!['@type-binding.type'].text).toBe('BaseModel');
});
it('excludes constructor arguments from record primary-constructor base bindings', () => {
const matches = emitCsharpScopeCaptures(
'record User(int id) : BaseEntity(id) { public void M() { base.Save(); } }',
'test.cs',
);
const baseMatch = matches.find(
(m) => '@type-binding.self' in m && m['@type-binding.name'].text === 'base',
);
expect(baseMatch?.['@type-binding.type'].text).toBe('BaseEntity');
});
it('preserves a plain record base binding', () => {
const matches = emitCsharpScopeCaptures(
'record User(int id) : BaseEntity { public void M() { base.Save(); } }',
'test.cs',
);
const baseMatch = matches.find(
(m) => '@type-binding.self' in m && m['@type-binding.name'].text === 'base',
);
expect(baseMatch?.['@type-binding.type'].text).toBe('BaseEntity');
});
it('excludes empty constructor arguments from record primary-constructor base bindings', () => {
const matches = emitCsharpScopeCaptures(
'record User(int id) : BaseEntity() { public void M() { base.Save(); } }',
'test.cs',
);
const baseMatch = matches.find(
(m) => '@type-binding.self' in m && m['@type-binding.name'].text === 'base',
);
expect(baseMatch?.['@type-binding.type'].text).toBe('BaseEntity');
});
it('excludes nested constructor arguments from record primary-constructor base bindings', () => {
const matches = emitCsharpScopeCaptures(
'record User(int id) : BaseEntity(Create(id, 2)) { public void M() { base.Save(); } }',
'test.cs',
);
const baseMatch = matches.find(
(m) => '@type-binding.self' in m && m['@type-binding.name'].text === 'base',
);
expect(baseMatch?.['@type-binding.type'].text).toBe('BaseEntity');
});
it('preserves qualified generic record primary-constructor base types without arguments', () => {
const matches = emitCsharpScopeCaptures(
'record User(int id) : App.BaseEntity<int>(id) { public void M() { base.Save(); } }',
'test.cs',
);
const baseMatch = matches.find(
(m) => '@type-binding.self' in m && m['@type-binding.name'].text === 'base',
);
expect(baseMatch?.['@type-binding.type'].text).toBe('App.BaseEntity<int>');
});
it('does not emit `this` or `base` for static methods', () => {
const matches = emitCsharpScopeCaptures('class User { public static void M() { } }', 'test.cs');
const receiverMatches = matches.filter((m) => '@type-binding.self' in m);