Skip to content

Replace heuristic c_import auto-defer with proven-ownership Drop wrappers #357

@ehartford

Description

@ehartford

Summary

BDFL ruling #16 changed the spec: c_import may make C resource cleanup ergonomic only when ownership is proven or explicitly asserted. Name heuristics alone may suggest candidates, but they must not insert cleanup, call destructors, generate owning wrappers, or mark raw C values as owned.

The current compiler still implements heuristic-only C destructor auto-defer.

Current behavior / suspected code paths

The current implementation has a complete name-heuristic auto-defer pipeline:

src/Sema.w:607-609
  ci_type_destructors / ci_auto_defer_bindings fields for c_import auto-defer

src/SemaDecl.w:457-480
  build_ci_destructor_map scans c_import-origin method names and records
  `.free`, `.destroy`, `.close`, `.unref`, `.release` as destructors

src/SemaCheck.w:4416-4432
  immutable let binding of a c_import pointer/struct type records an auto-defer
  destructor binding when the type has a name-matched destructor

src/MirLower.w:472-500
  emit_auto_defers lowers the recorded destructor call at scope exit

This is exactly the rejected rule: method-name detection becomes ownership and cleanup.

docs/completed/c-import-auto-methods.md also documents the old design as if auto-defer were the intended behavior; update/archive notes so future agents do not treat that completed design as current policy.

Expected behavior

c_import resource cleanup must use a two-step model:

  1. Establish ownership only from evidence that proves or asserts the contract:

    • explicit annotation;
    • author-supplied or imported metadata;
    • conservative source/header analysis strong enough to prove ownership;
    • curated library-specific conventions for known libraries;
    • hand-written owning wrappers.
  2. Express cleanup only through an owning wrapper type whose Drop calls the correct C destructor.

Name heuristics may produce candidates, import-manifest notes, or diagnostics suggesting a likely constructor/destructor pairing. They may not, by themselves:

  • insert cleanup;
  • call a destructor;
  • generate an owning wrapper;
  • mark a raw pointer/handle as owned.

Raw pointers and raw handles stay raw unless wrapped by a proven ownership model.

Reference-counted resources require ownership-specific modeling: Drop as unref is valid only for values built from an owning +1 constructor/retain/copy/create operation. Borrowing accessors produce non-owning handles with no Drop.

Root cause / five whys

  1. Why can the compiler call a C destructor the user did not request? Because ci_type_destructors records destructor-looking names and ci_auto_defer_bindings records cleanup for matching let bindings.
  2. Why are names enough? The old spec said prefix_destroy, prefix_free, prefix_close, prefix_unref, and prefix_release are destructors and allowed auto-defer for non-escaping let bindings.
  3. Why is that unsafe? C names do not prove ownership. Constructor-looking functions may return borrowed, singleton, arena-owned, retained, or otherwise non-owned handles; destructor-looking functions can have preconditions or apply only to specific ownership states.
  4. Why is scope-local auto-defer the wrong mechanism even with ownership? It ties cleanup to lexical scope instead of value lifetime and does not compose with moves, returns, or storage. The old escape suppression admits this: the compiler simply stops helping when the resource is moved/stored.
  5. Deepest cause: the importer conflates naming, ownership evidence, and cleanup mechanism. With needs a modeled owned value (Drop wrapper) for safe cleanup, not a destructor call bolted onto a raw C pointer by heuristic.

Acceptance criteria

  • Remove or disable heuristic ci_type_destructors / ci_auto_defer_bindings cleanup insertion.
  • build_ci_destructor_map no longer turns generic method names into ownership facts.
  • SemaCheck no longer records auto-defer bindings based only on c_import type + destructor-looking method.
  • MirLower no longer emits c_import heuristic auto-defers.
  • Add a regression showing a *_new + *_destroy pair does not automatically call destroy solely from names.
  • Add tests or design hooks for candidate metadata/diagnostics, without automatic cleanup.
  • Design and implement the future positive path separately: proven ownership metadata/annotation/curated convention/hand wrapper generates an owning Drop wrapper.
  • Reference-counted APIs distinguish owning +1 constructors/retains from borrowed accessors.
  • Update stale completed-design docs that still describe auto-defer as current behavior.

Spec references

  • docs/with-specification.md §16.2a Auto-Method Generation / Proven ownership cleanup
  • docs/requirements.md 16.2.2.11 through 16.2.2.27

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions