Skip to content

Commit 3846ed9

Browse files
committed
Improve module cycle diagnostics
1 parent 59bd3e2 commit 3846ed9

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ Goal: make multi-file script projects easier to compose and maintain.
252252
- [x] Add explicit export controls (beyond underscore naming).
253253
- [x] Add import aliasing for module objects.
254254
- [x] Define and enforce module namespace conflict behavior.
255-
- [ ] Improve cycle error diagnostics with concise chain rendering.
255+
- [x] Improve cycle error diagnostics with concise chain rendering.
256256
- [ ] Add module cache invalidation policy for long-running hosts.
257257

258258
### Security and Isolation

docs/integration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ end`)
7272

7373
The interpreter searches each configured directory for `<module>.vibe` in order
7474
and caches compiled modules so subsequent calls to `require` are inexpensive.
75+
When a circular module dependency is detected, the runtime reports a concise
76+
chain (for example `a -> b -> a`).
7577
Use the optional `as:` keyword to bind the loaded module object to a global
7678
alias.
7779
Inside a module, use explicit relative paths (`./` or `../`) to load siblings

vibes/modules.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ func moduleKeyDisplay(key string) string {
8181
return display
8282
}
8383

84+
func moduleDisplayName(key string) string {
85+
display := filepath.ToSlash(moduleKeyDisplay(key))
86+
return strings.TrimSuffix(display, ".vibe")
87+
}
88+
8489
func moduleRelativePath(root, fullPath string) (string, error) {
8590
rel, err := moduleRelativePathLexical(root, fullPath)
8691
if err != nil {
@@ -336,9 +341,19 @@ func moduleCycleFromExecution(stack []moduleContext, next string) ([]string, boo
336341
}
337342

338343
func formatModuleCycle(cycle []string) string {
339-
parts := make([]string, len(cycle))
340-
for idx, key := range cycle {
341-
parts[idx] = moduleKeyDisplay(key)
344+
if len(cycle) == 0 {
345+
return ""
346+
}
347+
normalized := make([]string, 0, len(cycle))
348+
for _, key := range cycle {
349+
if len(normalized) > 0 && normalized[len(normalized)-1] == key {
350+
continue
351+
}
352+
normalized = append(normalized, key)
353+
}
354+
parts := make([]string, len(normalized))
355+
for idx, key := range normalized {
356+
parts[idx] = moduleDisplayName(key)
342357
}
343358
return strings.Join(parts, " -> ")
344359
}
@@ -485,7 +500,8 @@ func builtinRequire(exec *Execution, receiver Value, args []Value, kwargs map[st
485500
}
486501

487502
if exec.moduleLoading[entry.key] {
488-
return NewNil(), fmt.Errorf("require: circular dependency detected for module %q", moduleKeyDisplay(entry.key))
503+
cycle := append(append([]string(nil), exec.moduleLoadStack...), entry.key)
504+
return NewNil(), fmt.Errorf("require: circular dependency detected: %s", formatModuleCycle(cycle))
489505
}
490506
exec.moduleLoading[entry.key] = true
491507
exec.moduleLoadStack = append(exec.moduleLoadStack, entry.key)

vibes/modules_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,3 +814,24 @@ end`)
814814
t.Fatalf("expected 12, got %#v", result)
815815
}
816816
}
817+
818+
func TestFormatModuleCycleUsesConciseChain(t *testing.T) {
819+
root := filepath.Join("tmp", "modules")
820+
a := moduleCacheKey(root, filepath.Join("nested", "a.vibe"))
821+
b := moduleCacheKey(root, filepath.Join("nested", "b.vibe"))
822+
823+
got := formatModuleCycle([]string{a, b, b, a})
824+
want := filepath.ToSlash(filepath.Join("nested", "a")) + " -> " + filepath.ToSlash(filepath.Join("nested", "b")) + " -> " + filepath.ToSlash(filepath.Join("nested", "a"))
825+
if got != want {
826+
t.Fatalf("expected cycle %q, got %q", want, got)
827+
}
828+
}
829+
830+
func TestModuleDisplayNameTrimsExtension(t *testing.T) {
831+
key := moduleCacheKey(filepath.Join("tmp", "modules"), filepath.Join("pkg", "helper.vibe"))
832+
got := moduleDisplayName(key)
833+
want := filepath.ToSlash(filepath.Join("pkg", "helper"))
834+
if got != want {
835+
t.Fatalf("expected display %q, got %q", want, got)
836+
}
837+
}

0 commit comments

Comments
 (0)