Skip to content
This repository was archived by the owner on Apr 29, 2026. It is now read-only.

Refactoring 0108#216

Merged
navicore merged 4 commits intomainfrom
refactoring-0108
Jan 9, 2026
Merged

Refactoring 0108#216
navicore merged 4 commits intomainfrom
refactoring-0108

Conversation

@navicore
Copy link
Copy Markdown
Owner

@navicore navicore commented Jan 9, 2026

  Results

  Code reduction: 51 net lines removed (112 deletions, 61 insertions)
  - File went from 1,894 → 1,843 lines

  Key changes:

  1. New simulate_word_common function (lines 440-563)
    - Handles all shared operations: resource creation/consumption, basic stack ops, strand.spawn, map/list storage, user-defined words
    - Takes a closure on_resource_dropped for diagnostic emission
    - Returns bool indicating if word was handled
  2. Simplified simulate_word_call (lines 565-569)
    - Now just 4 lines: calls shared helper with no-op callback
  3. Simplified analyze_word_call_with_context (lines 666-804)
    - Calls shared helper, collects dropped resources, emits warnings
    - Only contains pass-specific operations: strand.resume, over, rot, nip, tuck, 2dup, 3drop, pick, roll, chan.send/receive

  Benefits:
  - Single source of truth for resource simulation logic
  - Bug fixes now only need to be applied once
  - Easier to maintain and extend
  - All 15 resource_lint tests pass
  - No clippy warnings
  #208

  Results

  Code changes:
  - app.rs: -190 net lines (207 deleted, 17 added)
  - New stack_effects.rs: 185 lines (includes tests)
  - stack_art.rs: +10 lines (literal helper)
  - mod.rs: +1 line

  Net effect: The 215-line match statement is now a 15-line function that delegates to a declarative data table.

  Key improvements:

  1. Declarative definitions - Effects defined concisely:
  effect(&mut m, "dup", &["x"], &["x", "x"]);
  effect(&mut m, "swap", &["x", "y"], &["y", "x"]);
  typed_effect(&mut m, "i.+", &["Int", "Int"], &["Int"]);
  2. Easy to extend - Adding new effects is one line
  3. Static table - Effects compiled once using LazyLock
  4. Unit tests - 4 new tests for the effects module
  5. Clean separation - Effects logic isolated in ir/stack_effects.rs

  All 67 REPL tests pass, no clippy warnings.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 9, 2026

Code Review: Refactoring 0108

This PR successfully addresses issues #207 and #208 by deduplicating word simulation logic and extracting stack effects into a data structure. The refactoring is well-executed overall with some areas for consideration.


✅ Strengths

1. Excellent Deduplication in resource_lint.rs

The introduction of simulate_word_common effectively consolidates ~150+ lines of duplicated code between first and second pass analysis. The callback pattern for on_resource_dropped is elegant:

// First pass: no-op callback
Self::simulate_word_common(name, span, state, &self.word_info, |_| {});

// Second pass: collects dropped resources for warnings
Self::simulate_word_common(name, span, state, &self.word_info, |r| {
    dropped_resources.push(r.clone())
});

This ensures both passes use identical simulation logic while allowing the second pass to emit diagnostics.

2. Clean Separation of Concerns

The new stack_effects.rs module provides a declarative, data-driven approach to defining stack effects. The helper functions (effect, typed_effect, effect_with_output_types) reduce boilerplate significantly.

3. Good Test Coverage

The new module includes unit tests verifying lookup behavior and signature rendering, which is excellent practice.

4. Performance Optimization

Using LazyLock for the static effects table is the right choice for lazy initialization without runtime overhead.

5. Documentation

Inline comments in build_effects() (e.g., // dup ( ..a x -- ..a x x )) make the stack effects immediately understandable.


⚠️ Issues & Concerns

1. Bug: Inconsistent dup Behavior (Critical)

Location: resource_lint.rs:490-496

"dup" => {
    if let Some(top) = state.peek().cloned() {
        state.stack.push(top);
    } else {
        state.push_unknown();  // ← This is new behavior
    }
}

Issue: The original first-pass code did NOT push an unknown value when the stack was empty. This change potentially alters resource tracking behavior.

Impact: May cause false negatives in resource leak detection if dup is called on an empty stack.

Recommendation: Verify this is intentional. If not, restore the original behavior:

if let Some(top) = state.peek().cloned() {
    state.stack.push(top);
}
// Don't push anything if stack is empty

2. Incomplete Duplication Removal

Location: resource_lint.rs:710-795

The second-pass still contains duplicated implementations of over, rot, nip, and tuck that could have been moved to simulate_word_common. The difference is that nip and tuck emit warnings for dropped resources in the second pass.

Suggestion: Consider moving these to simulate_word_common as well:

"nip" => {
    let b = state.pop();
    let a = state.pop();
    if let Some(StackValue::Resource(r)) = a {
        let already_consumed = state.consumed.iter().any(|c| c.id == r.id);
        if !already_consumed {
            on_resource_dropped(&r);  // Use callback pattern
        }
    }
    if let Some(bv) = b {
        state.stack.push(bv);
    }
}

This would achieve even greater code reduction.

3. Missing Stack Effects

Location: stack_effects.rs:38-119

The new stack effects table is missing several operations that appear in resource_lint.rs:

  • strand.weave
  • strand.spawn
  • strand.resume
  • chan.make
  • chan.close
  • map.set
  • list.push / list.prepend
  • over, rot, nip, tuck (stack ops)

Impact: The REPL won't be able to display stack transition visualizations for these operations.

Recommendation: Add these to the effects table for completeness. For example:

// Resource operations
typed_effect(&mut m, "strand.weave", &["Quot"], &["WeaveHandle"]);
typed_effect(&mut m, "chan.make", &[], &["Channel"]);
typed_effect(&mut m, "strand.spawn", &["Quot"], &[]);

4. Code Duplication in Helper Functions

Location: stack_effects.rs:125-186

All three helper functions (effect, typed_effect, effect_with_output_types) share nearly identical loop structures:

let mut input_stack = Stack::with_rest("a");
for &v in inputs {
    input_stack = input_stack.push(/* ... */);
}

Suggestion: Extract a common helper to build stacks:

fn build_stack(rest: &str, items: impl Iterator<Item = StackValue>) -> Stack {
    let mut stack = Stack::with_rest(rest);
    for item in items {
        stack = stack.push(item);
    }
    stack
}

5. Potential Performance Concern

Location: resource_lint.rs:678-680

dropped_resources.push(r.clone())

Issue: Every dropped resource is cloned. If TrackedResource is large or contains expensive-to-clone fields, this could impact performance in programs with many resource operations.

Recommendation: Profile this if performance becomes a concern. Consider using Rc<TrackedResource> or passing references if cloning is expensive.


🔍 Security & Correctness

No Security Issues Detected

  • No unsafe code introduced
  • No new external dependencies
  • Resource tracking logic appears sound

Behavioral Equivalence

The refactoring maintains the same overall behavior with the exception of the dup edge case noted above. Both passes should produce identical results for valid programs.


📊 Test Coverage

Existing Tests

The existing resource_lint tests (lines 1351+) should catch any regressions in resource leak detection.

Recommendation: Add Integration Test

Consider adding a test that verifies both passes produce identical stack states for the same word sequence:

#[test]
fn test_pass_consistency() {
    let word = WordDef { /* ... */ };
    
    let mut first_pass_analyzer = /* ... */;
    let first_state = first_pass_analyzer.analyze_word(&word);
    
    let mut second_pass_analyzer = /* ... */;
    let second_state = second_pass_analyzer.analyze_word_with_context(&word);
    
    assert_eq!(first_state.stack, second_state.stack);
}

📈 Metrics

  • Lines removed: 319
  • Lines added: 294
  • Net reduction: 25 lines
  • Code duplication eliminated: ~150+ lines
  • New test coverage: 4 unit tests in stack_effects.rs

While the net line reduction is modest, the quality improvement is significant due to eliminated duplication and improved maintainability.


✨ Recommendations Summary

Must Fix (Critical)

  1. Verify dup behavior change on empty stack is intentional (resource_lint.rs:494)

Should Fix (Important)

  1. Add missing stack effects to the effects table
  2. Consider moving over/rot/nip/tuck to shared logic

Nice to Have

  1. Extract common stack-building logic
  2. Add integration test for pass consistency
  3. Profile resource cloning performance

🎯 Conclusion

This is a solid refactoring that successfully addresses both technical debt issues. The code is cleaner, more maintainable, and follows good separation of concerns. With the critical dup behavior verified and missing stack effects added, this PR will be ready to merge.

Great work on reducing duplication and improving code organization! 🚀

  Fixes Applied
  ┌────────────────────────┬─────────────────┬─────────────────────────────────────────────────────────────────────────┐
  │         Issue          │     Status      │                                 Change                                  │
  ├────────────────────────┼─────────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ Critical: dup behavior │ Fixed           │ Removed push_unknown() on empty stack - now matches original first-pass │
  ├────────────────────────┼─────────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ Missing stack effects  │ Already present │ over, rot, nip, tuck were already in stack_effects.rs                   │
  ├────────────────────────┼─────────────────┼─────────────────────────────────────────────────────────────────────────┤
  │ Consolidate stack ops  │ Done            │ Moved over, rot, nip, tuck from second-pass to simulate_word_common     │
  └────────────────────────┴─────────────────┴─────────────────────────────────────────────────────────────────────────┘
  Changes to resource_lint.rs

  - simulate_word_common now handles: dup, drop, swap, over, rot, nip, tuck, strand.spawn, map.set, list.push/prepend
  - analyze_word_call_with_context now only handles: strand.resume, 2dup, 3drop, pick, roll, chan.send/receive
  - nip uses the callback pattern for dropped resources (like drop does)
  - All 15 resource_lint tests pass

  The reviewer's other suggestions (extract common stack-building helper, add integration test, profile resource cloning) are "nice to have" and can be addressed in future PRs.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 9, 2026

Code Review - PR #216: Refactoring 0108

This PR successfully addresses issues #207 and #208 with well-executed refactoring. The changes demonstrate strong engineering practices and significantly improve code maintainability.


✅ Strengths

Issue #207: Resource Lint Deduplication

  • Excellent refactoring approach: The simulate_word_common function with callback pattern is clean and elegant
  • Single source of truth: Resource simulation logic is now centralized, reducing bug surface area
  • Smart use of closures: The on_resource_dropped callback allows first pass (no-op) and second pass (diagnostic emission) to share logic
  • Bug fix included: Fixed the critical dup behavior (line 490-495) to not push unknown on empty stack
  • Net reduction: 51 lines removed while improving code quality

Issue #208: Stack Effects Extraction

  • Data-driven design: New stack_effects.rs module uses declarative definitions instead of 200+ lines of match arms
  • LazyLock pattern: Static initialization is efficient and thread-safe
  • Helper functions: effect(), typed_effect(), and effect_with_output_types() provide clean DSL-like API
  • Unit tests included: 4 tests validate the effects module functionality
  • Literal helper: Added StackEffect::literal() helper improves consistency in app.rs
  • Massive reduction: 215-line match reduced to 15-line lookup function

🔍 Code Quality Observations

resource_lint.rs:440-613

Good:

  • Clear documentation explaining the return value contract
  • Proper handling of already_consumed checks prevents double-warnings
  • Stack effect comments (e.g., // ( ..a x y -- ..a y )) improve readability

Potential Improvements:

  1. Resource cloning (lines 561-568): The strand.spawn implementation clones all resources from the stack. Consider if this could be optimized with iteration instead:

    for item in state.stack.iter() {
        if let StackValue::Resource(r) = item {
            state.consume_resource(r.clone());
        }
    }
  2. Missing edge cases: over, rot, nip, tuck don't push unknown values when stack depth is insufficient. This is inconsistent with 2dup (line 767-768) which does. Consider:

    "over" => {
        if state.depth() >= 2 {
            let second = state.stack[state.depth() - 2].clone();
            state.stack.push(second);
        } else {
            state.push_unknown(); // Add this for consistency
        }
    }

stack_effects.rs:1-220

Good:

  • Clean module organization with comprehensive documentation
  • Excellent test coverage for the new module
  • Clear separation between variable effects and typed effects

Minor suggestions:

  1. Constants for common patterns (lines 19-121): Consider extracting the Stack::with_rest("a") pattern:

    const BASE_STACK: Stack = Stack::with_rest("a");

    But this may not be possible if Stack isn't const-constructible.

  2. Validation: Consider adding debug assertions or compile-time validation to ensure effect definitions are correct.


🛡️ Security & Correctness

No security concerns identified. The changes are purely internal refactoring with:

  • No changes to input validation
  • No new unsafe code
  • No changes to resource handling semantics

Correctness:

  • Per commit message, all 15 resource_lint tests pass ✅
  • All 67 REPL tests pass ✅
  • No clippy warnings ✅

📊 Test Coverage

Existing tests appear adequate:

  • resource_lint.rs has comprehensive tests (lines 1700+)
  • stack_effects.rs includes new unit tests
  • Integration testing covered by REPL tests

Recommendation: Consider adding an integration test that verifies first-pass and second-pass produce identical stack states (excluding diagnostics). This would catch any future divergence in the shared logic.


🎯 Performance Considerations

LazyLock usage (stack_effects.rs:11): Excellent choice. The effects table is computed once at first access and reused. Zero runtime overhead after initialization.

Resource cloning (resource_lint.rs:561-568, 547): Minor concern - resources are cloned during tuck and strand.spawn. For the resource tracker this is acceptable since TrackedResource is small (ID, kind, metadata).


📝 Documentation

Well documented overall:

  • Clear module-level docs in stack_effects.rs
  • Function-level comments explain the callback pattern
  • Stack effect comments use standard Forth notation

Could add:

  • Example usage in the simulate_word_common doc comment
  • Brief explanation of why certain ops (strand.resume, 2dup, etc.) remain in the second pass

✨ Overall Assessment

This is high-quality refactoring work:

Category Rating Notes
Code Quality ⭐⭐⭐⭐⭐ Clean, idiomatic Rust
Architecture ⭐⭐⭐⭐⭐ Smart use of callbacks and data-driven design
Testing ⭐⭐⭐⭐ Good coverage, could add one integration test
Documentation ⭐⭐⭐⭐ Clear, could be slightly more comprehensive
Impact ⭐⭐⭐⭐⭐ Significant maintainability improvement

Net change: -27 lines with dramatically improved maintainability.


✅ Recommendation: Approve with minor suggestions

The PR successfully achieves its goals and can be merged. The minor suggestions above (edge case handling, potential integration test) can be addressed in follow-up PRs if desired.

Great work addressing technical debt while maintaining code quality! 🎉

@navicore navicore merged commit 316728f into main Jan 9, 2026
2 checks passed
@navicore navicore deleted the refactoring-0108 branch January 9, 2026 05:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant