Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

103 lines
4.0 KiB
Go

package resolver
import "github.com/zzet/gortex/internal/graph"
// isCSharpExtension reports whether n is a C# extension method (a static method
// whose first parameter carries the `this` modifier). Such methods are bound
// only by the type-directed extension rule, never by the locality fallback. The
// Language check keeps this C#-only: other languages (e.g. Scala) also stamp
// Meta["extension"], and their locality resolution must be left unchanged.
func isCSharpExtension(n *graph.Node) bool {
if n == nil || n.Language != "csharp" || n.Meta == nil {
return false
}
v, _ := n.Meta["extension"].(bool)
return v
}
// csharpHasCompetingMethod reports whether a non-extension method of the same
// name is among the candidates. C# resolves an instance/interface member over
// an extension, so without receiver-type evidence the extension must not
// preempt a competing member the locality fallback would otherwise bind.
func csharpHasCompetingMethod(candidates []*graph.Node) bool {
for _, c := range candidates {
if c != nil && c.Kind == graph.KindMethod && !isCSharpExtension(c) {
return true
}
}
return false
}
// tryBindCSharpExtension binds a failed C# member call `x.Foo(...)` to a static
// extension method `Foo(this X x)`. It runs after the receiver-type passes (an
// instance or interface member always wins over an extension in C#) and before
// the locality fallback. Candidates are the raw same-name in-repo nodes, so a
// reachability drop cannot hide a valid extension. Returns true when it binds.
//
// Precision rules — never guess on ambiguity, which would recreate the
// same-name-wrong-type misattribution the receiver-type gate exists to prevent:
// - with receiver-type evidence: bind when exactly one extension's
// this_param_type matches the receiver; more than one stays unresolved.
// - without a matching type: bind only when the name maps to exactly one
// extension method in the repo; otherwise stay unresolved.
func (r *Resolver) tryBindCSharpExtension(e *graph.Edge, methodName, receiverType string, candidates []*graph.Node, stats *ResolveStats) bool {
// C#-only: a non-C# caller must never bind to a C# extension method even
// when a same-named one exists in a mixed-language repo.
if cn := r.cachedGetNode(e.From); cn == nil || cn.Language != "csharp" {
return false
}
var exts []*graph.Node
for _, c := range candidates {
if isCSharpExtension(c) {
exts = append(exts, c)
}
}
if len(exts) == 0 {
return false
}
// With receiver-type evidence, prefer the extension whose this_param_type
// matches the receiver. Exactly one match binds; more than one is an
// overload/ambiguity we refuse to guess on.
if receiverType != "" {
var typed []*graph.Node
for _, c := range exts {
if tp, _ := c.Meta["this_param_type"].(string); tp != "" && tp == receiverType {
typed = append(typed, c)
}
}
if len(typed) == 1 {
r.bindCSharpExtension(e, typed[0], 0.9, stats)
return true
}
if len(typed) > 1 {
return false
}
}
// No type evidence (or no typed match): bind only when the name is
// unambiguous across the repo's extension methods AND no non-extension
// member of that name competes (C# instance-method precedence — let the
// locality fallback bind the instance method instead).
if len(exts) == 1 && !csharpHasCompetingMethod(candidates) {
r.bindCSharpExtension(e, exts[0], 0.75, stats)
return true
}
return false
}
// bindCSharpExtension points a member-call edge at a resolved extension method
// at the ast_inferred tier — the binding is type-directed but not compiler-
// verified (extension visibility depends on `using` scope we do not fully model).
func (r *Resolver) bindCSharpExtension(e *graph.Edge, target *graph.Node, conf float64, stats *ResolveStats) {
e.To = target.ID
e.Origin = graph.OriginASTInferred
e.Confidence = conf
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeCalls, conf)
if e.Meta == nil {
e.Meta = map[string]any{}
}
e.Meta["resolution"] = "extension_method"
stats.Resolved++
}