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
103 lines
4.0 KiB
Go
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++
|
|
}
|