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

271 lines
7.0 KiB
Go

package resolver
import (
"strings"
"github.com/zzet/gortex/internal/graph"
)
// In-engine C++ overload resolution. Picks which same-named function/method a
// call binds to by ISO C++ rules — arity (with defaults + variadics), then
// implicit-conversion-sequence (ICS) ranking with pairwise dominance for the
// best-viable function — entirely from the signature metadata the cpp extractor
// stamps, no compiler needed. It runs in CI/sandbox where clangd cannot.
//
// Strict invariant (from GitNexus, sharpened): DEGRADE, NEVER LIE. Any axis it
// cannot decide keeps the candidate; a genuinely ambiguous best-viable set
// (≥2 non-dominated) returns nil so the resolver suppresses the edge rather
// than binding the wrong overload.
const cppRankInf = 1 << 30
// cppArithmetic is the set of normalized arithmetic base types eligible for
// standard arithmetic conversions (rank 2).
var cppArithmetic = map[string]bool{
"int": true, "double": true, "char": true, "bool": true,
"long": true, "short": true, "float": true, "unsigned": true,
}
// cppIntegralPromotion maps a small integral type to its promoted form
// (rank 1, better than a general arithmetic conversion).
var cppIntegralPromotion = map[string]string{
"char": "int", "bool": "int", "short": "int",
}
// cppShape is the decoded per-parameter indirection sidecar.
type cppShape struct {
isPointer bool
isLRef bool
isRRef bool
isConst bool
}
func decodeCppShape(code string) cppShape {
s := cppShape{}
if strings.HasPrefix(code, "c") {
s.isConst = true
code = code[1:]
}
switch code {
case "p":
s.isPointer = true
case "l":
s.isLRef = true
case "r":
s.isRRef = true
}
return s
}
// cppConversionRank returns the implicit-conversion-sequence rank from argType
// to paramType (lower = better): 0 exact, 1 integral promotion, 2 standard
// conversion (arithmetic, nullptr→T*, T*→bool, T*→void*), 3 nullptr→bool,
// 5 ellipsis, cppRankInf mismatch. (User-defined conversions — rank 4 — need a
// converting-ctor index not yet built; their absence means a UDC-only match is
// conservatively a non-match, never a wrong bind.)
func cppConversionRank(argType, paramType string, arg, param cppShape) int {
if argType == paramType {
if exactShapeCompatible(arg, param) {
return 0
}
return cppRankInf
}
if paramType == "..." {
return 5
}
if cppIntegralPromotion[argType] == paramType && paramType != "" {
return 1
}
if cppArithmetic[argType] && cppArithmetic[paramType] {
return 2
}
if argType == "null" && param.isPointer {
return 2
}
if argType == "null" && paramType == "bool" {
return 3
}
if arg.isPointer && paramType == "bool" {
return 2
}
if arg.isPointer && param.isPointer && paramType == "void" {
return 2
}
return cppRankInf
}
// exactShapeCompatible: an exact base-type match is only a rank-0 conversion
// when the indirection agrees (int ≠ int*). A value arg binds to a value or a
// (const) reference parameter; a pointer arg binds to a pointer parameter.
func exactShapeCompatible(a, p cppShape) bool {
return a.isPointer == p.isPointer
}
// cppCandSig is a candidate's parsed signature.
type cppCandSig struct {
node *graph.Node
paramTypes []string
shapes []cppShape
reqParams int
variadic bool
}
// parseCppCandidate reads the cpp_* signature Meta off a node. ok is false when
// the node carries no extracted signature (so the resolver can't rank it).
func parseCppCandidate(n *graph.Node) (cppCandSig, bool) {
if n == nil || n.Meta == nil {
return cppCandSig{}, false
}
if _, ok := n.Meta["cpp_sig"]; !ok {
return cppCandSig{}, false
}
c := cppCandSig{node: n}
if pt, _ := n.Meta["cpp_param_types"].(string); pt != "" {
c.paramTypes = strings.Split(pt, ",")
}
if ps, _ := n.Meta["cpp_param_shapes"].(string); ps != "" {
for _, code := range strings.Split(ps, ",") {
c.shapes = append(c.shapes, decodeCppShape(code))
}
}
c.reqParams = cppMetaInt(n.Meta, "cpp_req_params")
if _, ok := n.Meta["cpp_variadic"]; ok {
c.variadic = true
}
return c, true
}
func cppMetaInt(m map[string]any, k string) int {
switch v := m[k].(type) {
case int:
return v
case int64:
return int(v)
case float64:
return int(v)
}
return 0
}
// ResolveCppOverload selects the best-viable overload among same-name
// candidates, or nil to degrade to the caller's namespace cascade.
func ResolveCppOverload(argHints []string, candidates []*graph.Node) *graph.Node {
var sigs []cppCandSig
for _, c := range candidates {
if c == nil || (c.Kind != graph.KindFunction && c.Kind != graph.KindMethod) {
continue
}
s, ok := parseCppCandidate(c)
if !ok {
continue // no signature → not rankable; leave to the cascade
}
if !cppArityCompatible(s, len(argHints)) {
continue
}
sigs = append(sigs, s)
}
switch len(sigs) {
case 0:
return nil
case 1:
return sigs[0].node
}
// Multiple arity-viable candidates: need argument types to rank further.
if len(argHints) == 0 {
return nil // can't disambiguate → suppress
}
normArgs := make([]string, len(argHints))
for i, a := range argHints {
normArgs[i] = graph.NormalizeCppType(a)
}
argShapes := make([]cppShape, len(argHints)) // literal/value args; unknown = value
type ranked struct {
node *graph.Node
vec []int
}
var viable []ranked
for _, s := range sigs {
vec := make([]int, len(normArgs))
bad := false
for j := range normArgs {
if normArgs[j] == "" {
// Unknown arg type: compatible with any parameter, and neutral
// for dominance (every candidate scores 0 here). Degrade, never
// lie — an untyped arg never makes a candidate non-viable.
vec[j] = 0
continue
}
pt, psh := cppParamAt(s, j)
r := cppConversionRank(normArgs[j], pt, argShapes[j], psh)
if r >= cppRankInf {
bad = true
break
}
vec[j] = r
}
if !bad {
viable = append(viable, ranked{s.node, vec})
}
}
switch len(viable) {
case 0:
return nil
case 1:
return viable[0].node
}
// Pairwise dominance → non-dominated set ([over.ics.rank]).
var nondom []ranked
for i := range viable {
dominated := false
for k := range viable {
if i != k && cppDominates(viable[k].vec, viable[i].vec) {
dominated = true
break
}
}
if !dominated {
nondom = append(nondom, viable[i])
}
}
if len(nondom) == 1 {
return nondom[0].node
}
return nil // ≥2 non-dominated → ambiguous → suppress (never lie)
}
func cppParamAt(s cppCandSig, j int) (string, cppShape) {
if j < len(s.paramTypes) {
sh := cppShape{}
if j < len(s.shapes) {
sh = s.shapes[j]
}
return s.paramTypes[j], sh
}
if s.variadic {
return "...", cppShape{}
}
return "", cppShape{}
}
func cppArityCompatible(s cppCandSig, argCount int) bool {
if s.variadic {
return argCount >= s.reqParams
}
return argCount >= s.reqParams && argCount <= len(s.paramTypes)
}
// cppDominates: a is not-worse-everywhere and strictly-better-somewhere than b.
func cppDominates(a, b []int) bool {
better := false
for i := range a {
if a[i] > b[i] {
return false
}
if a[i] < b[i] {
better = true
}
}
return better
}