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
107 lines
3.4 KiB
Go
107 lines
3.4 KiB
Go
package resolver
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// DjangoDescriptorResolver is a claiming resolver for Django's named
|
|
// descriptor dispatch — an attribute reference the static graph cannot
|
|
// resolve because it names a runtime descriptor, not a declared method.
|
|
// The flagship case is `self._iterable_class(self)` inside a QuerySet:
|
|
// `_iterable_class` is a class attribute (default `ModelIterable`), and
|
|
// iterating its instance runs `ModelIterable.__iter__`. This resolver
|
|
// claims those residual `_iterable_class` references and binds them to the
|
|
// iterable class's `__iter__`, keyed by class names present in the graph.
|
|
type DjangoDescriptorResolver struct{}
|
|
|
|
// djangoDescriptorVocab is the set of Django descriptor attribute names this
|
|
// resolver claims. Kept tight so the pre-filter only sees its own framework
|
|
// vocabulary.
|
|
var djangoDescriptorVocab = map[string]bool{
|
|
"_iterable_class": true,
|
|
}
|
|
|
|
// djangoDefaultIterableClass is Django's default QuerySet._iterable_class.
|
|
const djangoDefaultIterableClass = "ModelIterable"
|
|
|
|
func (DjangoDescriptorResolver) Name() string { return SynthDjangoDescriptor }
|
|
|
|
// Claims reports whether the edge references a Django descriptor name.
|
|
func (DjangoDescriptorResolver) Claims(e *graph.Edge) bool {
|
|
if e == nil {
|
|
return false
|
|
}
|
|
return djangoDescriptorVocab[djangoRefName(e.To)]
|
|
}
|
|
|
|
// Resolve rebinds a claimed `_iterable_class` reference to the iterable
|
|
// class's `__iter__` method — the class named by the QuerySet's
|
|
// django_iterable_class hint, else Django's default ModelIterable.
|
|
func (DjangoDescriptorResolver) Resolve(g graph.Store, e *graph.Edge) bool {
|
|
if g == nil || e == nil || djangoRefName(e.To) != "_iterable_class" {
|
|
return false
|
|
}
|
|
iterableClass := djangoIterableClassFor(g, e.From)
|
|
if iterableClass == "" {
|
|
iterableClass = djangoDefaultIterableClass
|
|
}
|
|
target := djangoFindIterMethod(g, iterableClass)
|
|
if target == nil {
|
|
return false
|
|
}
|
|
oldTo := e.To
|
|
e.To = target.ID
|
|
e.Origin = graph.OriginASTInferred
|
|
e.Confidence = 0.7
|
|
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeCalls, 0.7)
|
|
StampSynthesized(e, SynthDjangoDescriptor)
|
|
g.ReindexEdges([]graph.EdgeReindex{{Edge: e, OldTo: oldTo}})
|
|
return true
|
|
}
|
|
|
|
// djangoRefName extracts the bare attribute name from an unresolved target
|
|
// id, stripping the `unresolved::` prefix and any `*.` method marker.
|
|
func djangoRefName(to string) string {
|
|
if !graph.IsUnresolvedTarget(to) {
|
|
return ""
|
|
}
|
|
return strings.TrimPrefix(graph.UnresolvedName(to), "*.")
|
|
}
|
|
|
|
// djangoIterableClassFor returns the iterable-class name hinted on the class
|
|
// enclosing the reference's source method, or "".
|
|
func djangoIterableClassFor(g graph.Store, fromID string) string {
|
|
n := g.GetNode(fromID)
|
|
if n == nil || n.Meta == nil {
|
|
return ""
|
|
}
|
|
recv, _ := n.Meta["receiver"].(string)
|
|
if recv == "" {
|
|
return ""
|
|
}
|
|
for _, c := range g.FindNodesByName(recv) {
|
|
if c == nil || c.Kind != graph.KindType || c.Meta == nil {
|
|
continue
|
|
}
|
|
if ic, _ := c.Meta["django_iterable_class"].(string); ic != "" {
|
|
return ic
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// djangoFindIterMethod returns the `__iter__` method of the named class.
|
|
func djangoFindIterMethod(g graph.Store, className string) *graph.Node {
|
|
for _, n := range g.FindNodesByName("__iter__") {
|
|
if n == nil || n.Kind != graph.KindMethod || n.Meta == nil {
|
|
continue
|
|
}
|
|
if recv, _ := n.Meta["receiver"].(string); recv == className {
|
|
return n
|
|
}
|
|
}
|
|
return nil
|
|
}
|