Skip to content

fix(commonlisp): give define-condition the inherits edges of its parent types - #3544

Open
fade wants to merge 2 commits into
Graphify-Labs:v8from
fade:commonlisp-define-condition-inherits
Open

fade wants to merge 2 commits into
Graphify-Labs:v8from
fade:commonlisp-define-condition-inherits

Conversation

@fade

@fade fade commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Builds on #3543. That PR's commit is included here as a base; the change this PR is about is the final commit, b26396b.

define-condition produces no inherits edge, even when the parent condition is defined in the
same file and both ends are present as nodes.

Conditions are how a Common Lisp program signals, so for a codebase that leans on them this leaves
a large part of its structure out of the graph.

It sits in _CL_DATA_DEFINERS, so it is handled by _handle_def_form, which records the name and
never reads the parent list. define-condition shares defclass's shape,
(NAME (PARENTS) (SLOTS) ...), so routing it to _handle_defclass gives it the inheritance edges
its parent types describe. The defclass path is unchanged.

Reproduction

(define-condition app-error (error) ())
(define-condition parse-error (app-error) ())
(defclass control () ())
(defclass control-sub (control) ())   ; control: this one already worked

Before: one inherits edge, from the defclass pair, none from the conditions.
After: three, with parse-error -> app-error and app-error -> error added.

test_cl_define_condition_emits_inherits_edges covers it and keeps the defclass pair as an
in-test control.

…ther file

A defmethod dispatching on a class defined in another file lost its specializes
edge. The specializer resolved to a file-scoped id with no backing node, so the
dangling-edge filter pruned the edge. Resolve through the same sourceless stub
the cross-file superclass path uses, so the corpus rewire collapses it onto the
real defclass; a same-file specializer still binds locally.

Most methods in a Common Lisp system live apart from the class they dispatch
on, so this removed nearly every specializes edge in a real codebase.

Add specializes to the supertype relations at the same time. Functions and
types occupy separate namespaces in Common Lisp, so one symbol is routinely
both, and without the guard a specializer stub can collapse onto a same-named
function and assert a dispatch relationship that does not exist.
…nt types

define-condition was routed to the generic definer path, which records the name
and never reads the parent list, so a condition hierarchy reached the graph as
unrelated nodes even with both ends in the same file. It shares defclass's
shape, (NAME (PARENTS) (SLOTS) ...), so the defclass handler covers it and the
class path keeps working unchanged.

Conditions are how a Common Lisp program signals, so for a codebase that leans
on them this left a large part of its structure invisible.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes Common Lisp method specializers so they resolve through ensure_class_ref instead of a file-scoped id, letting a method that dispatches on a class defined in another file keep its specializes edge as a sourceless stub rather than having it dropped by the dangling-edge filter. Adds specializes to _SUPERTYPE_RELATIONS so a specializer stub is treated as a type and never binds to a same-named function, since functions and types share names in CL. Routes define-condition through the defclass handler so condition hierarchies emit inherits edges from their parent lists.

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2283 functions depend on the 820 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 591 callers, 44 callees
  • new: _rebuild_code() — 116 callers, 51 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_commonlisp() — 30 callers, 6 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • new: run_pipeline() — 8 callers, 13 callees
  • …and 33 more — each is listed as a finding

Verification — 2283 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2108 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 276 test file(s) selected (39%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_commonlisp_specializer_binding.py — impact, changed-test
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • tests/test_java_member_calls.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_rewire\_unique\_stub\_nodes.

The verifier did not have enough to check \_rewire\_unique\_stub\_nodes, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: the input domain has 81 values but only 9 distinct were tested — a small finite domain must be EXHAUSTED, not sampled (an untested input could invert the result)

Could not verify: Could not verify extract\_commonlisp.

The verifier did not have enough to check extract\_commonlisp, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 41 more finding(s) on lines outside this diff (see the check run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant