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
271 lines
7.0 KiB
Go
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
|
|
}
|