Skip to content

Twist of a named strand inside a weave — (~a) is specified but unimplemented #96

Description

@hyperpolymath

Found by making the corpus gate evaluate conformance/valid, not merely parse it.

conformance/valid/v09_twist.tangle:

def twisted =
  weave strands a:Q into
    (~a)
  yield strands a:Q

parses, but fails to typecheck:

In definition 'twisted': Strand name 'a' cannot be used as a standalone
expression (use crossing syntax: a > b or a < b)

The spec says this should work

spec/grammar.ebnf:

(* -------------------- Twists --------------------------------- *)
(* In weave context: (~a) twists named strand a. *)
(* Standalone: (~expr) composes with all-strand twist. *)
(* The parser distinguishes by context (D1.18). *)

So (~a) inside a weave is a named-strand twist, and the corpus encodes exactly that.

Why it fails

infer_expr's Var case rejects any name found in the strand context:

| Var name ->
  begin match env_lookup gamma name with
  | Some (EVal ty) -> ty
  | ...
  | None ->
    begin match strand_lookup sigma name with
    | Some _entry ->
      type_error "Strand name '%s' cannot be used as a standalone expression
                  (use crossing syntax: a > b or a < b)" name

The message is right for a bare a, but (~a) is not bare — it is a Twist applied to a strand reference, which the spec explicitly permits. The typechecker has no case for it, so the strand-name guard fires first.

What is needed

  1. Type Twist (Var s) where s is in the strand context Σ — the result is the strand's type with a twist applied, per D1.18.
  2. Decide what a twisted strand means in the output boundary (does it change the strand type, or only the underlying word?).
  3. Evaluate it — eval currently has no weave-strand semantics at all; weave bodies only became reachable in feat(parser): weave is an expression, not only a statement (#88 B) #93, when weave was allowed in expression position.

Point 3 is worth flagging: until #93 a weave block could not be bound to anything, so its body was never evaluated. This construct has therefore never run, which is why the gap was invisible.

Status

Recorded in scripts/check-corpus.sh as CONFORMANCE_KNOWN_UNRUNNABLE, so it is visible, cannot regress further, and the ratchet will fail the build the moment it starts working and the entry goes stale.

Related: #94 (add{}, the other spec'd-but-unimplemented construct in the corpus).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions