@mark1russell7/coeval 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/static.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
2
- import { type Kind, type Namespace, type World, type WorldEntry, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
2
+ import { type Kind, type World, type WorldEntry, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
3
3
  import { ask } from "./ladder.ts";
4
4
  import { type PartSlot, partsOf, scopesOf } from "./parts.ts";
5
5
  import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
6
6
  import type { ErrorCode } from "./errors.ts";
7
7
  import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
8
8
  import type { ValueType } from "./types.ts";
9
- import { type RootTypes, type TypeNode, type Typed, typePass } from "./typing.ts";
9
+ import { type RootTypes, type TypeNode, type Typed, machineTypeIssues, typePass } from "./typing.ts";
10
+ import { cycleIssues } from "./instances.ts";
10
11
 
11
12
  /** One document that a walk visits. */
12
13
  export interface DocVisit {
@@ -528,19 +529,46 @@ export function typeCheck(machine: Machine, doc: unknown, options: CheckOptions
528
529
  /**
529
530
  * A problem of a world that `checkWorld` finds before a document exists (STATIC.COVERAGE). Each document of the
530
531
  * kind gives this problem for the verb, thus `check` with the verb finds it too.
532
+ *
533
+ * With the option `types`, a problem can also be a bad value type of the machine (TYPE.WORLD, D110). Such a problem has
534
+ * the code `#TYPE!` and the keys of the bad term in `at`.
531
535
  */
532
536
  export interface WorldIssue {
533
- /** The code of the problem. `#NOEVAL`: no rung of the ladder gives an evaluator for the verb. */
534
- readonly code: "#NOEVAL";
535
- /** The keys of the kind from the root of the world. For a fallback kind, the keys of its namespace. */
537
+ /**
538
+ * The code of the problem. `#NOEVAL`: no rung of the ladder gives an evaluator for the verb. `#TYPE!`: a value type
539
+ * of the machine is not correct, or a declaration of types cannot be true.
540
+ */
541
+ readonly code: "#NOEVAL" | "#TYPE!";
542
+ /**
543
+ * The keys of the kind from the root of the world. For a fallback kind, the keys of its namespace. For a type of a
544
+ * domain or of a type rule, the list is empty.
545
+ */
536
546
  readonly kind: readonly Key[];
537
547
  /** `true` for the fallback kind of a namespace or of the world. */
538
548
  readonly fallback?: true;
539
- /** The verb without an evaluator. */
549
+ /** The verb without an evaluator. A type issue has the verb `eval`, because the value types describe `eval` (D25). */
540
550
  readonly verb: string;
551
+ /** For a type issue of a domain: the name of the domain in `Machine.domains`. */
552
+ readonly domain?: string;
553
+ /** For a type issue of a type rule: the name of its evaluator in `Machine.evaluators`. */
554
+ readonly evaluator?: string;
555
+ /**
556
+ * For a type issue: the keys of the bad term in the kind, the domain or the declarations of the rule. An example is
557
+ * `["parts", "item", "expect"]`, or `["sigs", "add", 1, "params", 0]`.
558
+ */
559
+ readonly at?: readonly Key[];
541
560
  readonly reason: string;
542
561
  }
543
562
 
563
+ /** The options of `checkWorld`. */
564
+ export interface WorldOptions {
565
+ /**
566
+ * With `true`, the check also examines each value type of the machine (TYPE.WORLD, D110). It reads the contracts of
567
+ * the kinds, the domains and the declarations of the type rules. The default is `false`.
568
+ */
569
+ readonly types?: boolean;
570
+ }
571
+
544
572
  /** This function tells if a table of verbs has an entry for a verb. */
545
573
  function hasVerb(table: Readonly<Record<string, string>> | undefined, verb: string): boolean {
546
574
  return table !== undefined && Object.hasOwn(table, verb);
@@ -552,44 +580,34 @@ function hasVerb(table: Readonly<Record<string, string>> | undefined, verb: stri
552
580
  * evaluator for the verb, thus the check gives the same proof of coverage as a type system.
553
581
  *
554
582
  * The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
555
- * world. It does not follow an alias, because the kind of an alias has its own place. The check reads only the world.
583
+ * world. It does not follow an alias, because the kind of an alias has its own place. Without the option `types`, the
584
+ * check reads only the world.
585
+ *
586
+ * With the option `types`, the check also examines each value type of the machine (TYPE.WORLD, D110). These issues
587
+ * have the code `#TYPE!`, and they come after the issues of the verbs.
556
588
  *
557
589
  * @param machine - The machine, for its world.
558
590
  * @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
591
+ * @param options - The option `types`.
559
592
  * @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
560
593
  * order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
561
594
  */
562
- export function checkWorld(machine: Machine, verbs: readonly string[]): readonly WorldIssue[] {
595
+ export function checkWorld(machine: Machine, verbs: readonly string[], options: WorldOptions = {}): readonly WorldIssue[] {
563
596
  const world = machine.world;
564
597
  const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
565
598
  const out: WorldIssue[] = [];
566
- if (open.length === 0) return out;
567
- const visit = (kind: Kind, path: readonly Key[], fallback: boolean): void => {
568
- const text = path.join(".");
569
- const name = fallback ? (path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}`) : `the kind ${text}`;
570
- for (const verb of open) {
571
- if (hasVerb(kind.verbs, verb)) continue;
572
- const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
573
- out.push(fallback ? { code: "#NOEVAL", kind: path, fallback: true, verb, reason } : { code: "#NOEVAL", kind: path, verb, reason });
574
- }
575
- };
576
- // The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
577
- const way = new Set<Namespace>();
578
- const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
579
- for (const key of Object.keys(kinds)) {
580
- const entry = kinds[key];
581
- const here = [...path, key];
582
- if (isKind(entry)) visit(entry, here, false);
583
- else if (isNamespace(entry) && !way.has(entry)) {
584
- way.add(entry);
585
- entries(entry.kinds, here);
586
- if (entry.fallback !== undefined) visit(entry.fallback, here, true);
587
- way.delete(entry);
599
+ if (open.length > 0) {
600
+ eachKind(world, (kind, path, fallback) => {
601
+ const text = path.join(".");
602
+ const name = fallback ? (path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}`) : `the kind ${text}`;
603
+ for (const verb of open) {
604
+ if (hasVerb(kind.verbs, verb)) continue;
605
+ const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
606
+ out.push(fallback ? { code: "#NOEVAL", kind: path, fallback: true, verb, reason } : { code: "#NOEVAL", kind: path, verb, reason });
588
607
  }
589
- }
590
- };
591
- entries(world.kinds, []);
592
- if (world.fallback !== undefined) visit(world.fallback, [], true);
608
+ });
609
+ }
610
+ if (options.types === true) out.push(...machineTypeIssues(machine));
593
611
  return out;
594
612
  }
595
613
 
@@ -621,45 +639,316 @@ const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "ite
621
639
  * This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
622
640
  * `equations`, a `unit` and a `default` see no names. `check` and `factsOf` read the equations with this one function.
623
641
  */
624
- export function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
642
+ export function equationsOf(kind: Kind): readonly Equation[] {
625
643
  const tree = kind.tree;
626
644
  if (tree === undefined) return [];
627
- const out: { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] = [];
628
- for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({ label: `tree.equations.${name}`, term, names: [] });
629
- for (const [name, term] of Object.entries(tree.down ?? {})) out.push({ label: `tree.down.${name}`, term, names: DOWN_NAMES });
645
+ const out: Equation[] = [];
646
+ for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({ label: `tree.equations.${name}`, term, names: [], attribute: name, down: false });
647
+ for (const [name, term] of Object.entries(tree.down ?? {})) out.push({ label: `tree.down.${name}`, term, names: DOWN_NAMES, attribute: name, down: true });
630
648
  for (const [name, declared] of Object.entries(tree.attributes ?? {})) {
631
649
  if (declared.fold !== undefined) {
632
- out.push({ label: `tree.attributes.${name}.fold.unit`, term: declared.fold.unit, names: [] });
633
- out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES });
650
+ out.push({ label: `tree.attributes.${name}.fold.unit`, term: declared.fold.unit, names: [], attribute: name, down: false });
651
+ out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
634
652
  }
635
- if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [] });
653
+ if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [], attribute: name, down: false });
636
654
  }
637
655
  return out;
638
656
  }
639
657
 
658
+ /** One equation of the tree contract of a kind, with the names that it sees (D42). */
659
+ export interface Equation {
660
+ /** The path of the equation in the contract, for the reason of an issue. */
661
+ readonly label: string;
662
+ readonly term: Json;
663
+ readonly names: readonly string[];
664
+ /** The name of the attribute that the equation gives. */
665
+ readonly attribute: string;
666
+ /** `true` for an equation of `down`: it gives the attribute of a child. */
667
+ readonly down: boolean;
668
+ }
669
+
670
+ /**
671
+ * The steps that a node of the decorated tree answers before its ladder: `parent` and `type` (D106). An attribute with
672
+ * one of these names gets no read.
673
+ */
674
+ const STRUCTURAL: Readonly<Record<string, string>> = /* @__PURE__ */ Object.freeze({
675
+ parent: "the step parent gives the parent of the node",
676
+ type: "the step type gives the type of the node",
677
+ });
678
+
679
+ /**
680
+ * The standard code that gives its parts the self of its own context (D106). `check` knows the self of a document only
681
+ * through this code and the nodes of the decorated tree.
682
+ */
683
+ const KEEPS_SELF: ReadonlySet<string> = /* @__PURE__ */ new Set([
684
+ "data.number",
685
+ "data.text",
686
+ "data.bool",
687
+ "math.add",
688
+ "math.multiply",
689
+ "math.greater",
690
+ "control.seq",
691
+ "control.pure",
692
+ "control.if",
693
+ "list.all",
694
+ "effect.log",
695
+ "effect.write",
696
+ ]);
697
+
698
+ /** This function tells if standard code gives its parts the self of its own context (D106). */
699
+ export function keepsSelf(evaluator: string | undefined): boolean {
700
+ return evaluator !== undefined && KEEPS_SELF.has(evaluator);
701
+ }
702
+
703
+ /** The name of the standard code of a node of the decorated tree. */
704
+ export const TREE_NODE = "tree.node";
705
+
706
+ /** The name of the standard code of a reference. */
707
+ export const REF_CODE = "ref.resolve";
708
+
709
+ /** A node of the decorated tree that `check` knows (D106): the self of the code of its parts. */
710
+ export interface SeenNode {
711
+ readonly kind: Kind;
712
+ /** The name of the kind, for the reason of an issue. */
713
+ readonly text: string;
714
+ /** The names of the own parts of the node. The node of an equation has `undefined`: it stands for each node of its kind. */
715
+ readonly own: ReadonlySet<string> | undefined;
716
+ /** The parent node. It is `undefined` when `check` does not know the parent. */
717
+ readonly parent: SeenNode | undefined;
718
+ }
719
+
720
+ /** The self of the code of each document of one walk, for the checks of attributes and the graph of attribute instances (D106, D107). */
721
+ export interface Selves {
722
+ /**
723
+ * This function tells if a document is a node of the decorated tree whose attributes the walk knows.
724
+ *
725
+ * @param v - The visit of the document.
726
+ * @param evaluator - The evaluator that the ladder gives for the document.
727
+ */
728
+ isNode(v: DocVisit, evaluator: string | undefined): boolean;
729
+ /**
730
+ * This function gives the self of the code of a document, and its node when the document is a node. It records the
731
+ * self that the parts of the document get. The walk visits a document before its parts.
732
+ *
733
+ * @param v - The visit of the document.
734
+ * @param evaluator - The evaluator that the ladder gives for the document.
735
+ */
736
+ enter(v: DocVisit, evaluator: string | undefined): { readonly self: SeenNode | undefined; readonly node: SeenNode | undefined };
737
+ }
738
+
739
+ /**
740
+ * This function makes the selves of one walk (D106). Only `eval` reads attributes, and only when the root `self` is
741
+ * open. A node gets the self of its code as its parent. The standard code that keeps self gives its parts its own self.
742
+ * Other code gives its parts a self that the walk does not know.
743
+ *
744
+ * @param machine - The machine.
745
+ * @param verb - The verb of the walk.
746
+ * @param root - The self of the root document: the node of an equation, or `undefined`.
747
+ * @returns The selves.
748
+ */
749
+ export function selvesOf(machine: Machine, verb: string, root: SeenNode | undefined): Selves {
750
+ const roots: readonly string[] = machine.roots ?? ROOTS;
751
+ const on = verb === "eval" && roots.includes("self") && hasEntry(machine.evaluators, TREE_NODE) && !hasEntry(machine.runners, TREE_NODE);
752
+ const selves = new Map<DocVisit, SeenNode | undefined>();
753
+ const isNode = (v: DocVisit, evaluator: string | undefined): boolean => on && evaluator === TREE_NODE && v.kind !== undefined;
754
+ return {
755
+ isNode,
756
+ enter(v, evaluator) {
757
+ const self = v.parent === undefined ? root : selves.get(v.parent);
758
+ if (!isNode(v, evaluator) || v.kind === undefined) {
759
+ selves.set(v, keepsSelf(evaluator) ? self : undefined);
760
+ return { self, node: undefined };
761
+ }
762
+ // The own parts are the parts of the split, as in a run. A field without a part is data, and no step reads it.
763
+ const read = partsOf(machine.world, v.declared ? v.doc.value : v.doc, v.declared ? typeName(v.doc.type) : undefined);
764
+ const own = read.ok && read.split.ok ? new Set(read.split.parts.map((part) => part.key)) : undefined;
765
+ const node: SeenNode = { kind: v.kind, text: typeName(v.doc.type), own, parent: self };
766
+ selves.set(v, node);
767
+ return { self, node };
768
+ },
769
+ };
770
+ }
771
+
772
+ /**
773
+ * This function tells if a name is a structural step of a node of the decorated tree: `parent` or `type` (D106). An
774
+ * attribute with such a name gets no read.
775
+ */
776
+ export function isStructural(name: string): boolean {
777
+ return Object.hasOwn(STRUCTURAL, name);
778
+ }
779
+
780
+ /** One attribute that a kind needs at each of its nodes: a declaration, or a read in an equation (TREE.CHECK-COMPLETE). */
781
+ interface Need {
782
+ readonly name: string;
783
+ /** The words of the reason that tell why the kind needs the attribute. */
784
+ readonly why: string;
785
+ /** The attribute of the equation with the read. An own part with this name hides the equation. */
786
+ readonly hidden: string | undefined;
787
+ }
788
+
789
+ /** The state of the checks of attributes in one check: the kinds that the check walked, and the needs of each kind. */
790
+ interface TreeState {
791
+ readonly seen: Set<Kind>;
792
+ readonly needs: Map<Kind, Need[]>;
793
+ }
794
+
795
+ /** The names of `down` in the world of each machine, for a parent that `check` does not know (D106). */
796
+ const DOWNS = /* @__PURE__ */ new WeakMap<World, ReadonlySet<string>>();
797
+
798
+ /**
799
+ * This function gives each name that an equation of `down` of a kind of the world gives. The kinds include the kinds
800
+ * of the namespaces and each fallback kind (WORLD.OPEN). A parent that `check` does not know has one of these kinds.
801
+ */
802
+ export function downNames(world: World): ReadonlySet<string> {
803
+ const known = DOWNS.get(world);
804
+ if (known !== undefined) return known;
805
+ const out = new Set<string>();
806
+ const done = new Set<object>();
807
+ const add = (kind: Kind | undefined): void => {
808
+ for (const name of Object.keys(kind?.tree?.down ?? {})) out.add(name);
809
+ };
810
+ const visit = (entries: Readonly<Record<string, WorldEntry>>, fallback: Kind | undefined): void => {
811
+ if (done.has(entries)) return;
812
+ done.add(entries);
813
+ add(fallback);
814
+ for (const entry of Object.values(entries)) {
815
+ if (isKind(entry)) add(entry);
816
+ else if (isNamespace(entry)) visit(entry.kinds, entry.fallback);
817
+ }
818
+ };
819
+ visit(world.kinds, world.fallback);
820
+ DOWNS.set(world, out);
821
+ return out;
822
+ }
823
+
824
+ /** This function tells if a table of a tree contract has its own entry for a name. */
825
+ function hasEntry(table: Readonly<Record<string, unknown>> | undefined, name: string): boolean {
826
+ return table !== undefined && Object.hasOwn(table, name);
827
+ }
828
+
829
+ /**
830
+ * This function tells if a rung of the ladder can answer an attribute of a node (TREE.LADDER). An unknown parent can
831
+ * be a node of each kind of the world, thus each name of `down` in the world can answer. A copy can always answer,
832
+ * because `check` does not know a node without a parent.
833
+ */
834
+ export function answered(world: World, node: SeenNode, name: string): boolean {
835
+ if (node.own?.has(name) === true) return true;
836
+ const tree = node.kind.tree;
837
+ if (hasEntry(tree?.equations, name)) return true;
838
+ if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
839
+ const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
840
+ return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.default !== undefined);
841
+ }
842
+
640
843
  /**
641
844
  * This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
642
845
  * An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
643
- * of the document of the kind, and its reason names the equation.
846
+ * of the document of the kind, and its reason names the equation. The function also records the needs of the kind:
847
+ * each declared attribute, and each attribute of the node that an equation reads (TREE.CHECK-COMPLETE).
644
848
  */
645
- function equationIssues(machine: Machine, kind: Kind, kindText: string, options: CheckOptions, seen: Set<Kind>): readonly Issue[] {
849
+ function equationIssues(machine: Machine, kind: Kind, kindText: string, options: CheckOptions, state: TreeState): readonly Issue[] {
646
850
  const of = equationType(kind);
647
851
  const out: Issue[] = [];
648
- for (const { label, term, names } of equationsOf(kind)) {
852
+ const needs: Need[] = [];
853
+ state.needs.set(kind, needs);
854
+ for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
855
+ const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
856
+ for (const { label, term, names, attribute, down } of equationsOf(kind)) {
649
857
  const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
650
- for (const issue of checkAll(machine, term, inner, undefined, seen)) {
858
+ const read = (name: string): void => {
859
+ needs.push({ name, why: `the equation ${label} reads it`, hidden: down ? undefined : attribute });
860
+ };
861
+ for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
651
862
  out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
652
863
  }
653
864
  }
654
865
  return out;
655
866
  }
656
867
 
868
+ /**
869
+ * This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
870
+ * structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes.
871
+ */
872
+ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
873
+ const out: Issue[] = [];
874
+ const tree = kind.tree;
875
+ for (const table of ["equations", "down", "attributes"] as const) {
876
+ for (const name of Object.keys(tree?.[table] ?? {})) {
877
+ const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
878
+ if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
879
+ }
880
+ }
881
+ if (kind.side === "run") {
882
+ out.push({ code: "#CALC!", place: [], reason: `The tree kind ${kindText} is on the run side: a node evaluates its attributes on demand, thus in no fixed order.` });
883
+ }
884
+ return out;
885
+ }
886
+
887
+ /**
888
+ * This function finds the issues of a node of a document (D106). An own part with the name of a structural step is
889
+ * not read at any time (TREE.CHECK-RESERVED). Each need of the kind gets an answer from a rung (TREE.CHECK-COMPLETE).
890
+ */
891
+ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVisit): readonly Issue[] {
892
+ const out: Issue[] = [];
893
+ const at = (key: string): readonly Key[] => [...v.at, ...(v.declared ? [] : ["value"]), key];
894
+ for (const name of node.own ?? []) {
895
+ const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
896
+ if (why !== undefined) out.push({ code: "#NAME?", place: v.place, reason: `The part ${name} of the kind ${node.text} has a reserved name: ${why}.`, at: at(name) });
897
+ }
898
+ const done = new Set<string>();
899
+ for (const need of state.needs.get(node.kind) ?? []) {
900
+ if (done.has(need.name) || (need.hidden !== undefined && node.own?.has(need.hidden) === true)) continue;
901
+ done.add(need.name);
902
+ if (answered(machine.world, node, need.name)) continue;
903
+ out.push({ code: "#N/A", place: v.place, reason: `No rung gives the attribute ${need.name} of the kind ${node.text}, and ${need.why}.`, at: v.at });
904
+ }
905
+ return out;
906
+ }
907
+
908
+ /**
909
+ * This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
910
+ * nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
911
+ * function gives the index of the step and the reason. In an equation, a read of the node of the equation goes to the
912
+ * needs of its kind.
913
+ */
914
+ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly Json[], rootSelf: RootSelf | undefined): { readonly index: number; readonly reason: string } | undefined {
915
+ let node = self;
916
+ let index = 1;
917
+ while (node !== undefined && index < path.length && path[index] === "parent") {
918
+ node = node.parent;
919
+ index += 1;
920
+ }
921
+ const step = path[index];
922
+ if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
923
+ const name = String(step);
924
+ if (node.own === undefined) {
925
+ if (node === rootSelf?.node) rootSelf.read(name);
926
+ return undefined;
927
+ }
928
+ return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
929
+ }
930
+
931
+ /** The self of the root of a walk for the checks of attributes: the node of an equation, and the function for its reads. */
932
+ interface RootSelf {
933
+ readonly node: SeenNode;
934
+ /** This function gets each attribute of the node of the equation that the equation reads. */
935
+ readonly read: (name: string) => void;
936
+ }
937
+
657
938
  /**
658
939
  * This function finds the issues of `check`. With a list of nodes, it also records each document that it visits for
659
- * the pass of types. It marks each document with an issue. The set `seen` holds the kinds whose equations the check
660
- * walked, thus it walks the equations of each kind one time.
940
+ * the pass of types. It marks each document with an issue. The state holds the kinds whose equations the check
941
+ * walked, thus it walks the equations of each kind one time. In an equation, `rootSelf` is the node of the equation.
661
942
  */
662
- function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined, seen: Set<Kind> = new Set()): readonly Issue[] {
943
+ function checkAll(
944
+ machine: Machine,
945
+ doc: unknown,
946
+ options: CheckOptions,
947
+ nodes: TypeNode[] | undefined,
948
+ state: TreeState = { seen: new Set(), needs: new Map() },
949
+ rootSelf?: RootSelf,
950
+ ): readonly Issue[] {
951
+ const seen = state.seen;
663
952
  const issues: Issue[] = [];
664
953
  const verb = options.verb ?? "eval";
665
954
  const indices = nodes === undefined ? undefined : new Map<DocVisit, number>();
@@ -681,6 +970,11 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
681
970
  const anyName = options.names === "any";
682
971
  const roots: readonly string[] = machine.roots ?? ROOTS;
683
972
  const has = (table: Readonly<Record<string, unknown>> | undefined, name: string): boolean => table !== undefined && Object.hasOwn(table, name);
973
+ // The checks of attributes know the self of the code of each document (D106). Only eval reads attributes.
974
+ const attributes = verb === "eval" && roots.includes("self");
975
+ const selves = selvesOf(machine, verb, rootSelf?.node);
976
+ // A document with a node of the decorated tree gets the check of the cycles of its attributes (D107).
977
+ let trees = false;
684
978
  walk(
685
979
  machine.world,
686
980
  doc,
@@ -703,10 +997,16 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
703
997
  return "skip";
704
998
  }
705
999
  // The equations of a kind are code of the kind. The check walks them at the first document of the kind (D42).
706
- if (v.kind?.tree !== undefined && !seen.has(v.kind)) {
1000
+ const node = selves.isNode(v, answer.evaluator);
1001
+ if (v.kind !== undefined && (v.kind.tree !== undefined || node) && !seen.has(v.kind)) {
707
1002
  seen.add(v.kind);
708
- for (const issue of equationIssues(machine, v.kind, typeName(v.doc.type), options, seen)) push({ ...issue, place: v.place, at: v.at });
1003
+ const kindText = typeName(v.doc.type);
1004
+ for (const issue of [...contractIssues(v.kind, kindText), ...equationIssues(machine, v.kind, kindText, options, state)]) push({ ...issue, place: v.place, at: v.at });
709
1005
  }
1006
+ // The self of the code of this document, and the self that its parts get (D106).
1007
+ const { self, node: here } = selves.enter(v, answer.evaluator);
1008
+ if (here !== undefined) trees = true;
1009
+ if (here?.own !== undefined) for (const issue of nodeIssues(machine, here, state, v)) push(issue);
710
1010
  const name = answer.evaluator;
711
1011
  if (current !== undefined) current.evaluator = name;
712
1012
  // A runner hides the evaluator of its name (MACHINE.RUNNER), as in the compilation of a plan.
@@ -780,6 +1080,11 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
780
1080
  at: stepAt(1),
781
1081
  });
782
1082
  }
1083
+ // Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
1084
+ if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
1085
+ const issue = readIssue(machine, self, path, rootSelf);
1086
+ if (issue !== undefined) push({ code: "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
1087
+ }
783
1088
  path.forEach((step, index) => {
784
1089
  if (index === 0 || isKey(step)) return;
785
1090
  // The resolution finds the walker with the same function, thus the check and a run agree (VERB.LADDER).
@@ -811,5 +1116,7 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
811
1116
  issues.splice(drive.index, 0, drive.issue);
812
1117
  if (drive.node !== undefined) drive.node.marked = true;
813
1118
  }
1119
+ // The cycles of the attributes come after the issues of the walk (TREE.GRAPH-CYCLE). An equation gets none here.
1120
+ if (trees && rootSelf === undefined) for (const issue of cycleIssues(machine, doc, options)) issues.push(issue);
814
1121
  return issues;
815
1122
  }