A class instance refuses a key its class does not declare, read or written, through get, put, .field and [:key].
This commit is contained in:
parent
1022b7c7f9
commit
4eefd25df9
18
TODO.org
18
TODO.org
@ -587,10 +587,11 @@ method to a running program is an ordinary redefinition.
|
|||||||
** CANCELLED Class features deferred, each with its reason
|
** CANCELLED Class features deferred, each with its reason
|
||||||
CLOSED: [2026-09-20]
|
CLOSED: [2026-09-20]
|
||||||
Inheritance, multi-argument dispatch, =:before=/=:after=/=:around= and
|
Inheritance, multi-argument dispatch, =:before=/=:after=/=:around= and
|
||||||
=call-next-method=, named-slot construction, unknown-slot checking, computed
|
=call-next-method=, named-slot construction, compile-time unknown-slot checking,
|
||||||
dispatch values. With single dispatch on literal values there is no specificity
|
computed dispatch values. With single dispatch on literal values there is no
|
||||||
question, and inheritance or multiple dispatch would create one. Unknown-slot
|
specificity question, and inheritance or multiple dispatch would create one.
|
||||||
checking needs class-typed tracking the dyn side deliberately does not have.
|
Compile-time unknown-slot checking needs class-typed tracking the dyn side
|
||||||
|
deliberately does not have; the runtime refuses an unknown slot instead.
|
||||||
|
|
||||||
** DONE update-instance-for-redefined-class, the user hook
|
** DONE update-instance-for-redefined-class, the user hook
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
@ -732,8 +733,8 @@ of !=.
|
|||||||
|
|
||||||
** DONE A dyn value takes .field and [:key]
|
** DONE A dyn value takes .field and [:key]
|
||||||
CLOSED: [2026-09-26]
|
CLOSED: [2026-09-26]
|
||||||
Assigning ~x.name~ or ~m[:k]~ is ~put~, not the stricter ~(set (get x :k) v)~: it adds
|
Assigning ~x.name~ or ~m[:k]~ is ~put~: a plain map gains the key, and a class
|
||||||
a key a plain map or a class lacks. ~m[k]~ on a dyn map takes any key, as ~get~ does.
|
instance refuses one its class does not declare, on read too, as ~get~ and ~put~ now do.
|
||||||
|
|
||||||
** DONE A slice from a C pointer, and a pointer cast
|
** DONE A slice from a C pointer, and a pointer cast
|
||||||
CLOSED: [2026-09-25]
|
CLOSED: [2026-09-25]
|
||||||
@ -1402,9 +1403,8 @@ CLOSED: [2026-09-20]
|
|||||||
CLHS 4.3.6. Nothing is enumerated and no heap is walked — the
|
CLHS 4.3.6. Nothing is enumerated and no heap is walked — the
|
||||||
redefinition is constant time and each instance pays once, at its next touch.
|
redefinition is constant time and each instance pays once, at its next touch.
|
||||||
Neither printer migrates, so a stale instance shows its old slots to the editor
|
Neither printer migrates, so a stale instance shows its old slots to the editor
|
||||||
until something touches it. The registry is advisory: a key the class never
|
until something touches it. An instance holds only declared slots — get, put and
|
||||||
declared is dropped by the next migration, which is data loss with no enforcement
|
set refuse any other key — so the migration's drop loses nothing a program wrote.
|
||||||
behind it.
|
|
||||||
|
|
||||||
** WAIT A class registry keeps one slot list per class, not one per layout version
|
** WAIT A class registry keeps one slot list per class, not one per layout version
|
||||||
Decided 2026-09-25: waits for a case name-matching migration to the current list gets wrong.
|
Decided 2026-09-25: waits for a case name-matching migration to the current list gets wrong.
|
||||||
|
|||||||
@ -326,10 +326,9 @@ The CLOS answer would need, concretely:
|
|||||||
Flan spelling of that hook is a generic function, e.g.
|
Flan spelling of that hook is a generic function, e.g.
|
||||||
`(defmethod update-for-redefined point [p added discarded] ...)`, which fits
|
`(defmethod update-for-redefined point [p added discarded] ...)`, which fits
|
||||||
the dispatch mechanism that already exists.
|
the dispatch mechanism that already exists.
|
||||||
- A decision on whether `put` of an unknown slot stays legal. Today it is — a
|
- A decision on whether `put` of an unknown slot stays legal. Decided
|
||||||
class instance is an open map, and TODO.org, "Class features deferred, each with
|
2026-09-26: it does not; `get`, `put` and `set` refuse an unknown slot on an
|
||||||
its reason", already defers refusing an unknown slot at `(get p :z)`. If unknown slots stay legal, the registry's slot list is
|
instance at run time, so the registry's slot list is enforced.
|
||||||
advisory and the whole update protocol is advisory with it.
|
|
||||||
|
|
||||||
This is a real, SBCL/CLOS-precedented design that Flan's runtime can actually
|
This is a real, SBCL/CLOS-precedented design that Flan's runtime can actually
|
||||||
support. It is also a feature with no user yet, since redefinition on the dyn
|
support. It is also a feature with no user yet, since redefinition on the dyn
|
||||||
|
|||||||
@ -185,9 +185,9 @@ let collect (decls : Ast.decl list) =
|
|||||||
is what [class-of] answers and what a generic dispatches on.
|
is what [class-of] answers and what a generic dispatches on.
|
||||||
|
|
||||||
Named-slot construction — the dyn twin of [(Cursor {.src s})], with an
|
Named-slot construction — the dyn twin of [(Cursor {.src s})], with an
|
||||||
omitted slot meaning nil — is deferred, and so is refusing an unknown slot
|
omitted slot meaning nil — is deferred (TODO.org, "Class features deferred,
|
||||||
at [(get p :z)]. Both are recorded in TODO.org, "Class features deferred,
|
each with its reason"). An unknown slot, [(get p :z)], is refused at run
|
||||||
each with its reason". *)
|
time by the runtime's [trap_no_slot]. *)
|
||||||
let constructor n (slots : Ast.field list) loc : Ast.decl =
|
let constructor n (slots : Ast.field list) loc : Ast.decl =
|
||||||
(* Two slots of one name would write one entry and read one value, and the
|
(* Two slots of one name would write one entry and read one value, and the
|
||||||
constructor would take two arguments for it. The duplicate parameter
|
constructor would take two arguments for it. The duplicate parameter
|
||||||
|
|||||||
@ -1610,15 +1610,10 @@ flan_dyn flan_dyn_map_new(void) {
|
|||||||
*
|
*
|
||||||
* **What the registry constrains.** A store into a slot the class declares
|
* **What the registry constrains.** A store into a slot the class declares
|
||||||
* — the constructor's, [put]'s, [set]'s — is checked against the slot's
|
* — the constructor's, [put]'s, [set]'s — is checked against the slot's
|
||||||
* type. A key the class does not declare is not refused by [put]: a class
|
* type. A key the class does not declare is refused by [get], [put] and
|
||||||
* instance is an open map — TODO.org, "Class features deferred, each with its
|
* [set] alike ([trap_no_slot]), so an instance's keys are its class's slots
|
||||||
* reason", defers unknown-slot checking — so a key nobody declared can be
|
* and the migration below, which keeps only those, drops nothing a program
|
||||||
* written to one, and the migration below will *drop* it at the next
|
* wrote.
|
||||||
* redefinition, because its rule is that an instance's keys are the class's
|
|
||||||
* slots. That is real data loss and it is written down as such in TODO.org,
|
|
||||||
* "A redefined defclass migrates its instances lazily", rather than dressed
|
|
||||||
* up as enforcement. [set] does refuse an undeclared key, because a slot it
|
|
||||||
* writes has to exist.
|
|
||||||
*
|
*
|
||||||
* **Where a migration happens.** [want_map], so every [get], [put] and
|
* **Where a migration happens.** [want_map], so every [get], [put] and
|
||||||
* [has-key?]; [flan_dyn_len]'s map arm; and [dyn_equal]'s, so two instances
|
* [has-key?]; [flan_dyn_len]'s map arm; and [dyn_equal]'s, so two instances
|
||||||
@ -3206,10 +3201,20 @@ flan_dyn flan_dyn_map_get(flan_dyn m, flan_dyn k) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* A program's (get m k) and (.k m): the same, with the site a value that is
|
/* A program's (get m k) and (.k m): the same, with the site a value that is
|
||||||
* not a map is refused at. */
|
* not a map is refused at, and a key an instance's class does not declare
|
||||||
|
* refused rather than answered nil — [trap_no_slot]. */
|
||||||
|
static class_entry *class_sync(flan_obj *o);
|
||||||
|
static int64_t class_slot(class_entry *e, flan_dyn k);
|
||||||
|
static _Noreturn void trap_no_slot(const uint8_t *loc, int64_t loclen,
|
||||||
|
const char *op, flan_obj *o,
|
||||||
|
class_entry *e, flan_dyn k);
|
||||||
flan_dyn flan_dyn_get(flan_dyn m, flan_dyn k, const uint8_t *loc,
|
flan_dyn flan_dyn_get(flan_dyn m, flan_dyn k, const uint8_t *loc,
|
||||||
int64_t loclen) {
|
int64_t loclen) {
|
||||||
|
class_entry *e;
|
||||||
if (!is_map(m)) trap2(loc, loclen, TYPE_TRAP, "get", "only a map answers it", m, k);
|
if (!is_map(m)) trap2(loc, loclen, TYPE_TRAP, "get", "only a map answers it", m, k);
|
||||||
|
e = class_sync(dyn_obj(m));
|
||||||
|
if (e != NULL && class_slot(e, k) < 0)
|
||||||
|
trap_no_slot(loc, loclen, "get", dyn_obj(m), e, k);
|
||||||
return flan_dyn_map_get(m, k);
|
return flan_dyn_map_get(m, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3294,6 +3299,29 @@ static flan_dyn check_slot(const uint8_t *loc, int64_t loclen, int by,
|
|||||||
|
|
||||||
static inline void map_store(flan_obj *o, flan_dyn k, flan_dyn v);
|
static inline void map_store(flan_obj *o, flan_dyn k, flan_dyn v);
|
||||||
|
|
||||||
|
/* A key an instance's class does not declare, read or written. An instance
|
||||||
|
* has exactly its class's slots — a typo in a slot name is an error at the
|
||||||
|
* access and not a new key — so get, put and set all refuse one; a plain map
|
||||||
|
* takes any key. */
|
||||||
|
static _Noreturn void trap_no_slot(const uint8_t *loc, int64_t loclen,
|
||||||
|
const char *op, flan_obj *o,
|
||||||
|
class_entry *e, flan_dyn k) {
|
||||||
|
char sk[SAY_MAX];
|
||||||
|
kw_entry *c = o->u.v.klass;
|
||||||
|
int64_t i;
|
||||||
|
say(sk, SAY_MAX, k);
|
||||||
|
said_len = 0;
|
||||||
|
said_add("dyn %s: %.*s has no slot %s. Its slots are", op, (int)c->len,
|
||||||
|
(const char *)(c + 1), sk);
|
||||||
|
if (e == NULL || e->nslots == 0) said_add(" none");
|
||||||
|
else
|
||||||
|
for (i = 0; i < e->nslots; i++)
|
||||||
|
said_add(" :%.*s", (int)e->slots[i]->len,
|
||||||
|
(const char *)(e->slots[i] + 1));
|
||||||
|
flan_say(loc, loclen, "%s", said_buf);
|
||||||
|
flan_trap((const uint8_t *)"DynType", 7);
|
||||||
|
}
|
||||||
|
|
||||||
/* A constructor's stores: [flan_dyn_map_set]'s, with the refusal worded for
|
/* A constructor's stores: [flan_dyn_map_set]'s, with the refusal worded for
|
||||||
* the constructor call it happened inside rather than for a [put] nobody
|
* the constructor call it happened inside rather than for a [put] nobody
|
||||||
* wrote, and placed at the slot's declaration. */
|
* wrote, and placed at the slot's declaration. */
|
||||||
@ -3308,8 +3336,7 @@ void flan_dyn_slot_init(flan_dyn m, flan_dyn k, flan_dyn v,
|
|||||||
* they are three different mistakes: the value is not a class instance at
|
* they are three different mistakes: the value is not a class instance at
|
||||||
* all (a map's entries are written with [put], which is where inserting a
|
* all (a map's entries are written with [put], which is where inserting a
|
||||||
* key is real); the key is not a slot the class declares; the value does not
|
* key is real); the key is not a slot the class declares; the value does not
|
||||||
* fit the slot's type. The first two are why this is not [put]: a declared
|
* fit the slot's type. The first is why this is not [put]. */
|
||||||
* slot always exists, so writing one is a store and never an insertion. */
|
|
||||||
void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
||||||
const uint8_t *loc, int64_t loclen) {
|
const uint8_t *loc, int64_t loclen) {
|
||||||
flan_obj *o;
|
flan_obj *o;
|
||||||
@ -3329,43 +3356,37 @@ void flan_dyn_slot_set(flan_dyn m, flan_dyn k, flan_dyn v,
|
|||||||
o = dyn_obj(m);
|
o = dyn_obj(m);
|
||||||
e = class_sync(o);
|
e = class_sync(o);
|
||||||
j = class_slot(e, k);
|
j = class_slot(e, k);
|
||||||
if (j < 0) {
|
if (j < 0) trap_no_slot(loc, loclen, "set", o, e, k);
|
||||||
char sk[SAY_MAX];
|
|
||||||
kw_entry *c = o->u.v.klass;
|
|
||||||
int64_t i;
|
|
||||||
say(sk, SAY_MAX, k);
|
|
||||||
said_len = 0;
|
|
||||||
said_add("dyn set: %.*s has no slot %s. Its slots are",
|
|
||||||
(int)c->len, (const char *)(c + 1), sk);
|
|
||||||
if (e == NULL || e->nslots == 0) said_add(" none");
|
|
||||||
else
|
|
||||||
for (i = 0; i < e->nslots; i++)
|
|
||||||
said_add(" :%.*s", (int)e->slots[i]->len,
|
|
||||||
(const char *)(e->slots[i] + 1));
|
|
||||||
said_add("; a key the class does not declare is added with put, not set");
|
|
||||||
flan_say(loc, loclen, "%s", said_buf);
|
|
||||||
flan_trap((const uint8_t *)"DynType", 7);
|
|
||||||
}
|
|
||||||
if (!slot_admit(&e->types[j], v, &out))
|
if (!slot_admit(&e->types[j], v, &out))
|
||||||
trap_slot_type(loc, loclen, BY_SET, o, e, j, m, v);
|
trap_slot_type(loc, loclen, BY_SET, o, e, j, m, v);
|
||||||
map_store(o, k, out);
|
map_store(o, k, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
void flan_dyn_map_put(flan_dyn m, flan_dyn k, flan_dyn v, const uint8_t *loc,
|
static void map_put(flan_dyn m, flan_dyn k, flan_dyn v, const uint8_t *loc,
|
||||||
int64_t loclen) {
|
int64_t loclen, int any_key) {
|
||||||
flan_obj *o;
|
flan_obj *o;
|
||||||
class_entry *e;
|
class_entry *e;
|
||||||
if (!is_map(m)) trap2(loc, loclen, TYPE_TRAP, "put", "only a map answers it", m, k);
|
if (!is_map(m)) trap2(loc, loclen, TYPE_TRAP, "put", "only a map answers it", m, k);
|
||||||
o = dyn_obj(m);
|
o = dyn_obj(m);
|
||||||
e = class_sync(o);
|
e = class_sync(o);
|
||||||
|
if (!any_key && e != NULL && class_slot(e, k) < 0)
|
||||||
|
trap_no_slot(loc, loclen, "put", o, e, k);
|
||||||
/* A map with no class, and a class with no typed slot, stop at the test. */
|
/* A map with no class, and a class with no typed slot, stop at the test. */
|
||||||
if (e != NULL && e->typed) v = check_slot(loc, loclen, BY_PUT, o, e, m, k, v);
|
if (e != NULL && e->typed) v = check_slot(loc, loclen, BY_PUT, o, e, m, k, v);
|
||||||
map_store(o, k, v);
|
map_store(o, k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The same with no site: a map literal's stores, and test/dyn_ops.c. */
|
/* A program's put, and the store under a dyn's [.k] and [:k]. */
|
||||||
|
void flan_dyn_map_put(flan_dyn m, flan_dyn k, flan_dyn v, const uint8_t *loc,
|
||||||
|
int64_t loclen) {
|
||||||
|
map_put(m, k, v, loc, loclen, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With no site and any key: an untagged map literal's stores, and
|
||||||
|
* test/dyn_ops.c, which builds instances' odd states by hand. No program
|
||||||
|
* reaches an instance through it. */
|
||||||
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v) {
|
void flan_dyn_map_set(flan_dyn m, flan_dyn k, flan_dyn v) {
|
||||||
flan_dyn_map_put(m, k, v, NULL, 0);
|
map_put(m, k, v, NULL, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The store under all three, with the instance already brought up to date. */
|
/* The store under all three, with the instance already brought up to date. */
|
||||||
|
|||||||
@ -156,10 +156,8 @@ flan_dyn flan_dyn_type_of(flan_dyn v);
|
|||||||
* declares are dropped. The instance's identity is preserved throughout;
|
* declares are dropped. The instance's identity is preserved throughout;
|
||||||
* this is CLHS 4.3.6, and [flan_dyn_class_hook] is its user hook.
|
* this is CLHS 4.3.6, and [flan_dyn_class_hook] is its user hook.
|
||||||
*
|
*
|
||||||
* The drop is unconditional, which is the honest cost of a class instance
|
* No key the class never declared can be there to drop: [get], [put] and
|
||||||
* being an open map: a key written by a raw [put] that the class never
|
* [set] refuse one on an instance. */
|
||||||
* declared is dropped by the next migration too. The registry describes the
|
|
||||||
* class's intention and does not enforce it. */
|
|
||||||
void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n);
|
void flan_dyn_class_def(flan_dyn name, const uint8_t *slots, int64_t n);
|
||||||
|
|
||||||
/* The body update-instance-for-redefined-class dispatches through, as a
|
/* The body update-instance-for-redefined-class dispatches through, as a
|
||||||
|
|||||||
@ -28,12 +28,9 @@
|
|||||||
(println (get s :step))
|
(println (get s :step))
|
||||||
(set (get s :tag) [1 2])
|
(set (get s :tag) [1 2])
|
||||||
(println (get s :tag))
|
(println (get s :tag))
|
||||||
;; put reaches the same check for a declared slot, and still inserts a
|
;; put reaches the same check for a declared slot.
|
||||||
;; key the class does not declare -- an instance is an open map to put.
|
|
||||||
(put s :speed 2.5)
|
(put s :speed 2.5)
|
||||||
(put s :scratch 9)
|
|
||||||
(println (get s :speed))
|
(println (get s :speed))
|
||||||
(println (get s :scratch))
|
|
||||||
(println (length s))
|
(println (length s))
|
||||||
;; A typed caller boxes into the dyn parameter as any call does.
|
;; A typed caller boxes into the dyn parameter as any call does.
|
||||||
(set (get s :step) (twelve))
|
(set (get s :step) (twelve))
|
||||||
|
|||||||
@ -64,7 +64,9 @@
|
|||||||
(println (get p :x))
|
(println (get p :x))
|
||||||
(println (has-key? p :x))
|
(println (has-key? p :x))
|
||||||
(println (has-key? p :nothing))
|
(println (has-key? p :nothing))
|
||||||
(println (get p :nothing))
|
;; A key its class does not declare is refused on an instance; a plain
|
||||||
|
;; map answers nil for one it lacks.
|
||||||
|
(println (get {:x 1} :nothing))
|
||||||
|
|
||||||
;; The shape tag, as a value. Every value can be asked; only an instance
|
;; The shape tag, as a value. Every value can be asked; only an instance
|
||||||
;; answers with a name.
|
;; answers with a name.
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
(defn as-dyn [d dyn] dyn d)
|
(defn as-dyn [d dyn] dyn d)
|
||||||
|
|
||||||
(defn main [args [string]] i32
|
(defn main [args [str]] i32
|
||||||
(let [which (if (> (length args) 1) (i32 (bytes->i64 (bytes-view (at args 1)))) 0)
|
(let [which (if (> (length args) 1) (i32 (bytes->i64 (bytes-view (at args 1)))) 0)
|
||||||
s (State false false)
|
s (State false false)
|
||||||
n (as-dyn 3)]
|
n (as-dyn 3)]
|
||||||
@ -14,5 +14,7 @@
|
|||||||
(= which 1) (set (.paused s) 1)
|
(= which 1) (set (.paused s) 1)
|
||||||
(= which 2) (set (.paused n) true)
|
(= which 2) (set (.paused n) true)
|
||||||
(= which 3) (set (at s :step) 2)
|
(= which 3) (set (at s :step) 2)
|
||||||
|
(= which 5) (set (.pasued s) true)
|
||||||
|
(= which 6) (println (.pasued s))
|
||||||
:else (println (at n :paused))))
|
:else (println (at n :paused))))
|
||||||
0)
|
0)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ defclass(State, [paused bool step bool])
|
|||||||
|
|
||||||
fn as-dyn(d) -> dyn = d
|
fn as-dyn(d) -> dyn = d
|
||||||
|
|
||||||
fn main(args: [string]) -> i32
|
fn main(args: [str]) -> i32
|
||||||
let which =
|
let which =
|
||||||
if length(args) > 1 then i32(bytes->i64(bytes-view(args[1]))) else 0
|
if length(args) > 1 then i32(bytes->i64(bytes-view(args[1]))) else 0
|
||||||
let s = State(false, false)
|
let s = State(false, false)
|
||||||
@ -18,6 +18,10 @@ fn main(args: [string]) -> i32
|
|||||||
n.paused = true
|
n.paused = true
|
||||||
elif which == 3
|
elif which == 3
|
||||||
s[:step] = 2
|
s[:step] = 2
|
||||||
|
elif which == 5
|
||||||
|
s.pasued = true
|
||||||
|
elif which == 6
|
||||||
|
println(s.pasued)
|
||||||
else
|
else
|
||||||
println(n[:paused])
|
println(n[:paused])
|
||||||
0
|
0
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
(set (at (.items b) 0) 11)
|
(set (at (.items b) 0) 11)
|
||||||
(update (at (.items b) 1) + 5)
|
(update (at (.items b) 1) + 5)
|
||||||
(println (.count b) (.x (.pos b)) (.y (.pos b)) (at (.items b) 0) (at (.items b) 1))
|
(println (.count b) (.x (.pos b)) (.y (.pos b)) (at (.items b) 0) (at (.items b) 1))
|
||||||
(println (.missing b) (= (.missing b) (get b :missing))))
|
(println (.missing {:a 1}) (= (.missing {:a 1}) (get {:a 1} :missing))))
|
||||||
(let [m {:hp 3}]
|
(let [m {:hp 3}]
|
||||||
(set (.hp m) (- (.hp m) 1))
|
(set (.hp m) (- (.hp m) 1))
|
||||||
(set (at m :mp) 9)
|
(set (at m :mp) 9)
|
||||||
|
|||||||
@ -23,7 +23,8 @@ fn main() -> i32
|
|||||||
b.items[0] = 11
|
b.items[0] = 11
|
||||||
b.items[1] += 5
|
b.items[1] += 5
|
||||||
println(b.count, b.pos.x, b.pos.y, b.items[0], b.items[1])
|
println(b.count, b.pos.x, b.pos.y, b.items[0], b.items[1])
|
||||||
println(b.missing)
|
let plain = {:a 1}
|
||||||
|
println(plain.missing)
|
||||||
let m = {:hp 3}
|
let m = {:hp 3}
|
||||||
m.hp -= 1
|
m.hp -= 1
|
||||||
m[:mp] = 9
|
m[:mp] = 9
|
||||||
|
|||||||
@ -5652,13 +5652,12 @@ level "1"
|
|||||||
|
|
||||||
(* Typed class slots and set on a slot: the stores that fit, then one
|
(* Typed class slots and set on a slot: the stores that fit, then one
|
||||||
run per refusal. The constructor, put and set each check a declared
|
run per refusal. The constructor, put and set each check a declared
|
||||||
slot's type, set refuses a slot the class does not declare and a
|
slot's type, and set refuses a slot the class does not declare and a
|
||||||
value that is not an instance, and put still inserts an undeclared
|
value that is not an instance. On both backends, because every one of these is a runtime call
|
||||||
key. On both backends, because every one of these is a runtime call
|
|
||||||
whose arguments the two emit separately. *)
|
whose arguments the two emit separately. *)
|
||||||
let slots_out =
|
let slots_out =
|
||||||
"#state{:pause false :step 3 :speed 1.5 :name \"sand\" :tag :x}\n\
|
"#state{:pause false :step 3 :speed 1.5 :name \"sand\" :tag :x}\n\
|
||||||
true\n-7\n[1 2]\n2.5\n9\n6\n12\n3.5\ntrue\n2\n:state\n"
|
true\n-7\n[1 2]\n2.5\n5\n12\n3.5\ntrue\n2\n:state\n"
|
||||||
in
|
in
|
||||||
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
||||||
outputs ~x86:true "dyn: typed class slots, --x86"
|
outputs ~x86:true "dyn: typed class slots, --x86"
|
||||||
@ -5688,8 +5687,7 @@ level "1"
|
|||||||
is declared i32, and 5000000000 is not a value it holds \
|
is declared i32, and 5000000000 is not a value it holds \
|
||||||
exactly");
|
exactly");
|
||||||
("3", "dyn-slot-trap.flan:17:19: dyn set: state has no slot :paws. \
|
("3", "dyn-slot-trap.flan:17:19: dyn set: state has no slot :paws. \
|
||||||
Its slots are :pause :step :tag; a key the class does not \
|
Its slots are :pause :step :tag");
|
||||||
declare is added with put, not set");
|
|
||||||
("4", "dyn-slot-trap.flan:18:19: dyn set: (get m k) is a place only \
|
("4", "dyn-slot-trap.flan:18:19: dyn set: (get m k) is a place only \
|
||||||
on a class instance, and this is a map with no class");
|
on a class instance, and this is a map with no class");
|
||||||
("5", "dyn-slot-trap.flan:19:28: dyn construct: the slot :owner of \
|
("5", "dyn-slot-trap.flan:19:28: dyn construct: the slot :owner of \
|
||||||
@ -5733,7 +5731,9 @@ level "1"
|
|||||||
rows;
|
rows;
|
||||||
(try Sys.remove exe with Sys_error _ -> ())
|
(try Sys.remove exe with Sys_error _ -> ())
|
||||||
in
|
in
|
||||||
let field_rows (l0, l1, l2, l3, l4) =
|
(* 5 and 6 are a misspelt slot on a class instance, written and read:
|
||||||
|
refused naming the class and its slots, never a new key or a nil. *)
|
||||||
|
let field_rows (l0, l1, l2, l3, l4, l5, l6) =
|
||||||
[ ("0", l0 ^ ": dyn get: int and keyword, and only a map answers it — \
|
[ ("0", l0 ^ ": dyn get: int and keyword, and only a map answers it — \
|
||||||
(get 3 :paused)");
|
(get 3 :paused)");
|
||||||
("1", l1 ^ ": dyn put: the slot :paused of State is declared bool, \
|
("1", l1 ^ ": dyn put: the slot :paused of State is declared bool, \
|
||||||
@ -5744,19 +5744,27 @@ level "1"
|
|||||||
("3", l3 ^ ": dyn put: the slot :step of State is declared bool, and \
|
("3", l3 ^ ": dyn put: the slot :step of State is declared bool, and \
|
||||||
this is int");
|
this is int");
|
||||||
("4", l4 ^ ": dyn at: int and keyword, and only a text, a vec or a \
|
("4", l4 ^ ": dyn at: int and keyword, and only a text, a vec or a \
|
||||||
map is indexed — (at 3 :paused)") ]
|
map is indexed — (at 3 :paused)");
|
||||||
|
("5", l5 ^ ": dyn put: State has no slot :pasued. Its slots are \
|
||||||
|
:paused :step");
|
||||||
|
("6", l6 ^ ": dyn get: State has no slot :pasued. Its slots are \
|
||||||
|
:paused :step") ]
|
||||||
in
|
in
|
||||||
let flan_rows =
|
let flan_rows =
|
||||||
field_rows ("dyn-field-trap.flan:13:28", "dyn-field-trap.flan:14:19",
|
field_rows ("dyn-field-trap.flan:13:28", "dyn-field-trap.flan:14:19",
|
||||||
"dyn-field-trap.flan:15:19", "dyn-field-trap.flan:16:19",
|
"dyn-field-trap.flan:15:19", "dyn-field-trap.flan:16:19",
|
||||||
"dyn-field-trap.flan:17:22")
|
"dyn-field-trap.flan:19:22", "dyn-field-trap.flan:17:19",
|
||||||
|
"dyn-field-trap.flan:18:28")
|
||||||
|
and fln_rows =
|
||||||
|
field_rows ("dyn-field-trap.fln:14:13", "dyn-field-trap.fln:16:5",
|
||||||
|
"dyn-field-trap.fln:18:5", "dyn-field-trap.fln:20:5",
|
||||||
|
"dyn-field-trap.fln:26:13", "dyn-field-trap.fln:22:5",
|
||||||
|
"dyn-field-trap.fln:24:13")
|
||||||
in
|
in
|
||||||
field_trap "programs/dyn-field-trap.flan" flan_rows;
|
field_trap "programs/dyn-field-trap.flan" flan_rows;
|
||||||
field_trap ~x86:true "programs/dyn-field-trap.flan" flan_rows;
|
field_trap ~x86:true "programs/dyn-field-trap.flan" flan_rows;
|
||||||
field_trap "programs/dyn-field-trap.fln"
|
field_trap "programs/dyn-field-trap.fln" fln_rows;
|
||||||
(field_rows ("dyn-field-trap.fln:14:13", "dyn-field-trap.fln:16:5",
|
field_trap ~x86:true "programs/dyn-field-trap.fln" fln_rows;
|
||||||
"dyn-field-trap.fln:18:5", "dyn-field-trap.fln:20:5",
|
|
||||||
"dyn-field-trap.fln:22:13"));
|
|
||||||
|
|
||||||
(* A numeric cast opening a dyn box — TODO.org, "A numeric cast opens a
|
(* A numeric cast opening a dyn box — TODO.org, "A numeric cast opens a
|
||||||
dyn box". programs/dyn-cast.flan is one program because the three
|
dyn box". programs/dyn-cast.flan is one program because the three
|
||||||
|
|||||||
@ -9346,13 +9346,13 @@ let () =
|
|||||||
|
|
||||||
(* ── A slot lost, and a second generation ──
|
(* ── A slot lost, and a second generation ──
|
||||||
[:y] goes. Nothing calls [area] after this: its method reads :y,
|
[:y] goes. Nothing calls [area] after this: its method reads :y,
|
||||||
which is now nil, and a generic that traps on a slot its class no
|
which is now refused, and a generic that traps on a slot its class no
|
||||||
longer has is the program being wrong rather than the migration. *)
|
longer has is the program being wrong rather than the migration. *)
|
||||||
let r = redefine "(defclass point [x z])" in
|
let r = redefine "(defclass point [x z])" in
|
||||||
if status r <> "ok" then fail "removing a slot from a class: %s" (said r)
|
if status r <> "ok" then fail "removing a slot from a class: %s" (said r)
|
||||||
else begin
|
else begin
|
||||||
holds "a lost slot reads as absent"
|
holds "a lost slot is absent"
|
||||||
"(if (= (get (at instances 0) :y) nil) 1 0)";
|
"(if (has-key? (at instances 0) :y) 0 1)";
|
||||||
holds "a lost slot is gone from the count"
|
holds "a lost slot is gone from the count"
|
||||||
"(if (= (length (at instances 0)) 2) 1 0)";
|
"(if (= (length (at instances 0)) 2) 1 0)";
|
||||||
holds "the slots either side of it are untouched"
|
holds "the slots either side of it are untouched"
|
||||||
@ -9385,31 +9385,13 @@ let () =
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
(* ── A definition that did not change ──
|
(* ── A definition that did not change ──
|
||||||
Every C-c C-k re-runs a file's class definitions, and a generation
|
Every C-c C-k re-runs a file's class definitions, and re-running
|
||||||
bumped per registration rather than per *change* would migrate
|
an unchanged one has to leave its instances' values alone. *)
|
||||||
every instance in the program on every save. Here that would be
|
|
||||||
visible: the value written below is put into a slot the class
|
|
||||||
declares, and a spurious migration would keep it — so the
|
|
||||||
discriminating half is the raw key on the line after, which a real
|
|
||||||
migration drops and an ignored re-registration leaves alone. *)
|
|
||||||
holds "a key written straight into an instance"
|
|
||||||
"(do (put (at instances 0) :scratch 7) 1)";
|
|
||||||
let r = redefine "(defclass point [x z w])" in
|
let r = redefine "(defclass point [x z w])" in
|
||||||
if status r <> "ok" then fail "re-evaluating an unchanged class: %s" (said r)
|
if status r <> "ok" then fail "re-evaluating an unchanged class: %s" (said r)
|
||||||
else
|
else
|
||||||
holds "an unchanged definition migrates nothing"
|
holds "an unchanged definition keeps the values"
|
||||||
"(if (= (get (at instances 0) :scratch) 7) 1 0)";
|
"(if (= (get (at instances 1) :x) 5) 1 0)"
|
||||||
(* And the same key after a definition that *did* change, which is
|
|
||||||
the advisory registry stated as a test rather than as a hope: a
|
|
||||||
class instance is an open map, [put] accepts any key, and the next
|
|
||||||
migration drops the ones the class does not declare. TODO.org, "A
|
|
||||||
redefined defclass migrates its instances lazily", says so in as
|
|
||||||
many words. *)
|
|
||||||
let r = redefine "(defclass point [x z w q])" in
|
|
||||||
if status r <> "ok" then fail "a fourth redefinition: %s" (said r)
|
|
||||||
else
|
|
||||||
holds "a migration drops a key the class never declared"
|
|
||||||
"(if (= (get (at instances 0) :scratch) nil) 1 0)"
|
|
||||||
end;
|
end;
|
||||||
(try Unix.close c with Unix.Unix_error _ -> ());
|
(try Unix.close c with Unix.Unix_error _ -> ());
|
||||||
(try Unix.kill mpid Sys.sigkill with Unix.Unix_error _ -> ());
|
(try Unix.kill mpid Sys.sigkill with Unix.Unix_error _ -> ());
|
||||||
|
|||||||
@ -728,8 +728,9 @@ kind as a keyword — <code>:nil</code>, <code>:bool</code>, <code>:int</code>,
|
|||||||
<code>:keyword</code> — and an instance's class name, so a class cannot be named
|
<code>:keyword</code> — and an instance's class name, so a class cannot be named
|
||||||
after one of those kinds. The slots are map keys: <code>(.pause s)</code>
|
after one of those kinds. The slots are map keys: <code>(.pause s)</code>
|
||||||
reads one, and <code>(set (.pause s) true)</code> writes one, checking its
|
reads one, and <code>(set (.pause s) true)</code> writes one, checking its
|
||||||
type. <code>get</code> and <code>put</code> do the same, and <code>put</code>
|
type. <code>get</code> and <code>put</code> do the same. A key the class does
|
||||||
is also how a key the class does not declare is added.</p>
|
not declare is refused, so a misspelled slot stops the program at the line that
|
||||||
|
misspelled it.</p>
|
||||||
|
|
||||||
<p>Dispatch comes in the two styles and they are one mechanism.
|
<p>Dispatch comes in the two styles and they are one mechanism.
|
||||||
<code>defgeneric</code> dispatches on the class of the first argument, which is
|
<code>defgeneric</code> dispatches on the class of the first argument, which is
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user