fix(resolution): a call through a declared member calls that member's type (#2171)

C# / Java / Kotlin receivers that are fields, properties or parameters were
typed only when declared in the calling method without generics, so a call
through a field fell to the receiver-word guess: Newtonsoft's
_innerWriter.WriteValue() landed on the wrapper's own WriteValue, a
TextWriter's Write() on a test writer's, commons-lang's DateFormat field's
parse() on DateParser 284 times.

- read class-level member declarations (brace depth 1, comments skipped),
  walking base classes with their type arguments substituted, C# using
  aliases, type-parameter bounds, generic and for-each declarations
- C# interface members default public and namespace-level types internal
  (TS extractor + kernel, parity 0 diffs); interface calls resolve at all
- the receiver-word guess weighs the owner type's name only, the receiver's
  last link (a constant's type), and its head noun; .NET std names need a
  receiver named after the owner; production code never lands in a test
  suite's mock

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-30 14:54:31 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a39fd14e89
commit f9530d7d5f
6 changed files with 685 additions and 13 deletions
+13 -2
View File
@@ -418,7 +418,8 @@ impl<'t> Walker<'t> {
// Java-style `modifiers` wrapper (probed).
/// getVisibility: FIRST `modifier` child whose text is one of the four
/// levels wins; none → private (the C# default).
/// levels wins; none → public in an interface, internal in a namespace
/// or the file, private in a class or struct (the C# defaults).
fn visibility_of(&self, node: Node) -> u8 {
for i in 0..node.child_count() {
let Some(child) = node.child(i) else { continue };
@@ -432,7 +433,17 @@ impl<'t> Walker<'t> {
}
}
}
2 // C# defaults to private
let parent = node.parent();
let container = match parent {
Some(p) if p.kind() == "declaration_list" => p.parent(),
other => other,
};
match container.map(|c| c.kind()) {
Some("interface_declaration") => 1,
None | Some("compilation_unit") | Some("namespace_declaration")
| Some("file_scoped_namespace_declaration") => 4,
_ => 2,
}
}
fn is_static(&self, node: Node) -> bool {