Skip to content

Commit 291c3c5

Browse files
committed
fix(python): preserve inheritance fact compatibility
Fixing regression in the golden test.
1 parent b05c950 commit 291c3c5

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

internal/extractors/pythonextractor/python_ast.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ func (w *pyWalker) handleFromImport(node *sitter.Node) {
477477
// flag them as orphans. Kept only here to avoid bloating every from-import.
478478
isInit := w.relFile == "__init__.py" || strings.HasSuffix(w.relFile, "/__init__.py")
479479
var reexported []string
480+
bindings := make(map[string]string)
480481

481482
// Map each imported name to a resolvable target or "" (external).
482483
for i := uint(0); i < uint(node.ChildCount()); i++ {
@@ -501,6 +502,9 @@ func (w *pyWalker) handleFromImport(node *sitter.Node) {
501502
importedName = pyText(c, w.src)
502503
localName = importedName
503504
}
505+
if localName != "" && importedName != "" && importedName != "*" {
506+
bindings[localName] = importedName
507+
}
504508

505509
if isInit && importedName != "" && importedName != "*" {
506510
reexported = append(reexported, importedName)
@@ -522,6 +526,9 @@ func (w *pyWalker) handleFromImport(node *sitter.Node) {
522526
}
523527
}
524528

529+
if len(bindings) > 0 {
530+
depProps["bindings"] = bindings
531+
}
525532
if len(reexported) > 0 {
526533
depProps["reexports"] = reexported
527534
}
@@ -795,9 +802,6 @@ func (w *pyWalker) handleClass(node *sitter.Node, decorators []string) {
795802
switch c.Kind() {
796803
case "identifier":
797804
base := pyText(c, w.src)
798-
if imported := w.importMap[base]; imported != "" {
799-
base = imported
800-
}
801805
bases = append(bases, base)
802806
rels = append(rels, facts.Relation{Kind: facts.RelImplements, Target: base})
803807
case "attribute":

internal/extractors/pythonextractor/resolve.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,30 @@ func resolveImplementsTargets(allFacts []facts.Fact, fileModules map[string]bool
119119
reexports := buildReexportIndex(allFacts, pkgDirs)
120120
symbols := make(map[string]bool)
121121
byShortName := make(map[string][]string)
122+
importedByFile := make(map[string]map[string]string)
122123
for i := range allFacts {
124+
if allFacts[i].Kind == facts.KindDependency && allFacts[i].Props["from"] == true {
125+
var module string
126+
for _, rel := range allFacts[i].Relations {
127+
if rel.Kind == facts.RelImports {
128+
module = rel.Target
129+
break
130+
}
131+
}
132+
if module != "" {
133+
if importedByFile[allFacts[i].File] == nil {
134+
importedByFile[allFacts[i].File] = make(map[string]string)
135+
}
136+
for local, imported := range stringMapProp(allFacts[i].Props, "bindings") {
137+
target := module + "." + imported
138+
if previous, exists := importedByFile[allFacts[i].File][local]; exists && previous != target {
139+
importedByFile[allFacts[i].File][local] = ""
140+
} else {
141+
importedByFile[allFacts[i].File][local] = target
142+
}
143+
}
144+
}
145+
}
123146
if allFacts[i].Kind != facts.KindSymbol {
124147
continue
125148
}
@@ -131,6 +154,11 @@ func resolveImplementsTargets(allFacts []facts.Fact, fileModules map[string]bool
131154
}
132155
byShortName[short] = append(byShortName[short], name)
133156
}
157+
for i := range allFacts {
158+
if allFacts[i].Kind == facts.KindDependency && allFacts[i].Props != nil {
159+
delete(allFacts[i].Props, "bindings")
160+
}
161+
}
134162

135163
for i := range allFacts {
136164
f := &allFacts[i]
@@ -151,6 +179,12 @@ func resolveImplementsTargets(allFacts []facts.Fact, fileModules map[string]bool
151179
}
152180
continue
153181
}
182+
if imported := importedByFile[f.File][rel.Target]; imported != "" {
183+
if resolved, keep := resolveDottedTarget(imported, fileIdx, topPkgs, fileDir(f.File), reexports, symbols); keep && symbols[resolved] {
184+
rel.Target = resolved
185+
}
186+
continue
187+
}
154188
candidates := byShortName[rel.Target]
155189
if len(candidates) == 1 {
156190
rel.Target = candidates[0]
@@ -288,6 +322,23 @@ func stringSliceProp(props map[string]any, key string) []string {
288322
return nil
289323
}
290324

325+
func stringMapProp(props map[string]any, key string) map[string]string {
326+
out := make(map[string]string)
327+
switch v := props[key].(type) {
328+
case map[string]string:
329+
for k, value := range v {
330+
out[k] = value
331+
}
332+
case map[string]any:
333+
for k, value := range v {
334+
if s, ok := value.(string); ok {
335+
out[k] = s
336+
}
337+
}
338+
}
339+
return out
340+
}
341+
291342
// resolveDottedTarget maps a dotted call target ("a.b.c.sym") to a canonical slash
292343
// symbol name when its module prefix resolves to an internal file or to a package
293344
// that re-exports the symbol, keeps it dotted when the prefix is internal but

0 commit comments

Comments
 (0)