mirror of
https://github.com/janet-lang/janet
synced 2024-11-28 11:09:54 +00:00
Add compile time arity checking.
This should help catch a number of errors, but it is a very shallow implementation of type checking. It will catch some common misuses of functions at compile time rather than runtime.
This commit is contained in:
parent
2becd196dd
commit
7910a5feef
@ -2,6 +2,7 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
- Add compile time arity checking when function in function call is known.
|
||||||
- Added `slice` to the core library.
|
- Added `slice` to the core library.
|
||||||
- The `*/slice` family of functions now can take nil as start or end to get
|
- The `*/slice` family of functions now can take nil as start or end to get
|
||||||
the same behavior as the defaults (0 and -1) for those parameters.
|
the same behavior as the defaults (0 and -1) for those parameters.
|
||||||
|
@ -320,33 +320,46 @@ JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Push slots load via janetc_toslots. */
|
/* Push slots loaded via janetc_toslots. Return the minimum number of slots pushed,
|
||||||
void janetc_pushslots(JanetCompiler *c, JanetSlot *slots) {
|
* or -1 - min_arity if there is a splice. (if there is no splice, min_arity is also
|
||||||
|
* the maximum possible arity). */
|
||||||
|
int32_t janetc_pushslots(JanetCompiler *c, JanetSlot *slots) {
|
||||||
int32_t i;
|
int32_t i;
|
||||||
int32_t count = janet_v_count(slots);
|
int32_t count = janet_v_count(slots);
|
||||||
|
int32_t min_arity = 0;
|
||||||
|
int has_splice = 0;
|
||||||
for (i = 0; i < count;) {
|
for (i = 0; i < count;) {
|
||||||
if (slots[i].flags & JANET_SLOT_SPLICED) {
|
if (slots[i].flags & JANET_SLOT_SPLICED) {
|
||||||
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i], 0);
|
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i], 0);
|
||||||
i++;
|
i++;
|
||||||
|
has_splice = 1;
|
||||||
} else if (i + 1 == count) {
|
} else if (i + 1 == count) {
|
||||||
janetc_emit_s(c, JOP_PUSH, slots[i], 0);
|
janetc_emit_s(c, JOP_PUSH, slots[i], 0);
|
||||||
i++;
|
i++;
|
||||||
|
min_arity++;
|
||||||
} else if (slots[i + 1].flags & JANET_SLOT_SPLICED) {
|
} else if (slots[i + 1].flags & JANET_SLOT_SPLICED) {
|
||||||
janetc_emit_s(c, JOP_PUSH, slots[i], 0);
|
janetc_emit_s(c, JOP_PUSH, slots[i], 0);
|
||||||
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i + 1], 0);
|
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i + 1], 0);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
min_arity++;
|
||||||
|
has_splice = 1;
|
||||||
} else if (i + 2 == count) {
|
} else if (i + 2 == count) {
|
||||||
janetc_emit_ss(c, JOP_PUSH_2, slots[i], slots[i + 1], 0);
|
janetc_emit_ss(c, JOP_PUSH_2, slots[i], slots[i + 1], 0);
|
||||||
i += 2;
|
i += 2;
|
||||||
|
min_arity += 2;
|
||||||
} else if (slots[i + 2].flags & JANET_SLOT_SPLICED) {
|
} else if (slots[i + 2].flags & JANET_SLOT_SPLICED) {
|
||||||
janetc_emit_ss(c, JOP_PUSH_2, slots[i], slots[i + 1], 0);
|
janetc_emit_ss(c, JOP_PUSH_2, slots[i], slots[i + 1], 0);
|
||||||
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i + 2], 0);
|
janetc_emit_s(c, JOP_PUSH_ARRAY, slots[i + 2], 0);
|
||||||
i += 3;
|
i += 3;
|
||||||
|
min_arity += 2;
|
||||||
|
has_splice = 1;
|
||||||
} else {
|
} else {
|
||||||
janetc_emit_sss(c, JOP_PUSH_3, slots[i], slots[i + 1], slots[i + 2], 0);
|
janetc_emit_sss(c, JOP_PUSH_3, slots[i], slots[i + 1], slots[i + 2], 0);
|
||||||
i += 3;
|
i += 3;
|
||||||
|
min_arity += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return has_splice ? (-1 - min_arity) : min_arity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a list of slots has any spliced slots */
|
/* Check if a list of slots has any spliced slots */
|
||||||
@ -403,7 +416,68 @@ static JanetSlot janetc_call(JanetFopts opts, JanetSlot *slots, JanetSlot fun) {
|
|||||||
/* TODO janet function inlining (no c functions)*/
|
/* TODO janet function inlining (no c functions)*/
|
||||||
}
|
}
|
||||||
if (!specialized) {
|
if (!specialized) {
|
||||||
janetc_pushslots(c, slots);
|
int32_t min_arity = janetc_pushslots(c, slots);
|
||||||
|
/* Check for provably incorrect function calls */
|
||||||
|
if (fun.flags & JANET_SLOT_CONSTANT) {
|
||||||
|
|
||||||
|
/* Check for bad arity type if fun is a constant */
|
||||||
|
switch (janet_type(fun.constant)) {
|
||||||
|
case JANET_FUNCTION:
|
||||||
|
{
|
||||||
|
JanetFunction *f = janet_unwrap_function(fun.constant);
|
||||||
|
int32_t min = f->def->min_arity;
|
||||||
|
int32_t max = f->def->max_arity;
|
||||||
|
if (min_arity < 0) {
|
||||||
|
/* Call has splices */
|
||||||
|
min_arity = -1 - min_arity;
|
||||||
|
if (min_arity > max && max >= 0) {
|
||||||
|
const uint8_t *es = janet_formatc(
|
||||||
|
"%v expects at most %d argument, got at least %d",
|
||||||
|
fun.constant, max, min_arity);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Call has no splices */
|
||||||
|
if (min_arity > max && max >= 0) {
|
||||||
|
const uint8_t *es = janet_formatc(
|
||||||
|
"%v expects at most %d argument, got %d",
|
||||||
|
fun.constant, max, min_arity);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
if (min_arity < min) {
|
||||||
|
const uint8_t *es = janet_formatc(
|
||||||
|
"%v expects at least %d argument, got %d",
|
||||||
|
fun.constant, min, min_arity);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JANET_CFUNCTION:
|
||||||
|
case JANET_ABSTRACT:
|
||||||
|
break;
|
||||||
|
case JANET_KEYWORD:
|
||||||
|
if (min_arity == 0) {
|
||||||
|
const uint8_t *es = janet_formatc("%v expects at least 1 argument, got 0",
|
||||||
|
fun.constant);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (min_arity > 1 || min_arity == 0) {
|
||||||
|
const uint8_t *es = janet_formatc("%v expects 1 argument, got %d",
|
||||||
|
fun.constant, min_arity);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
if (min_arity < -2) {
|
||||||
|
const uint8_t *es = janet_formatc("%v expects 1 argument, got at least %d",
|
||||||
|
fun.constant, -1 - min_arity);
|
||||||
|
janetc_error(c, es);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((opts.flags & JANET_FOPTS_TAIL) &&
|
if ((opts.flags & JANET_FOPTS_TAIL) &&
|
||||||
/* Prevent top level tail calls for better errors */
|
/* Prevent top level tail calls for better errors */
|
||||||
!(c->scope->flags & JANET_SCOPE_TOP)) {
|
!(c->scope->flags & JANET_SCOPE_TOP)) {
|
||||||
|
@ -214,7 +214,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len);
|
|||||||
JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds);
|
JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds);
|
||||||
|
|
||||||
/* Push slots load via janetc_toslots. */
|
/* Push slots load via janetc_toslots. */
|
||||||
void janetc_pushslots(JanetCompiler *c, JanetSlot *slots);
|
int32_t janetc_pushslots(JanetCompiler *c, JanetSlot *slots);
|
||||||
|
|
||||||
/* Free slots loaded via janetc_toslots */
|
/* Free slots loaded via janetc_toslots */
|
||||||
void janetc_freeslots(JanetCompiler *c, JanetSlot *slots);
|
void janetc_freeslots(JanetCompiler *c, JanetSlot *slots);
|
||||||
|
@ -78,11 +78,15 @@
|
|||||||
|
|
||||||
# Another regression test - no segfaults
|
# Another regression test - no segfaults
|
||||||
(defn afn [x] x)
|
(defn afn [x] x)
|
||||||
(assert (= 1 (try (afn) ([err] 1))) "bad arity 1")
|
(var afn-var afn)
|
||||||
|
(var identity-var identity)
|
||||||
|
(var map-var map)
|
||||||
|
(var not-var not)
|
||||||
|
(assert (= 1 (try (afn-var) ([err] 1))) "bad arity 1")
|
||||||
(assert (= 4 (try ((fn [x y] (+ x y)) 1) ([_] 4))) "bad arity 2")
|
(assert (= 4 (try ((fn [x y] (+ x y)) 1) ([_] 4))) "bad arity 2")
|
||||||
(assert (= 1 (try (identity) ([err] 1))) "bad arity 3")
|
(assert (= 1 (try (identity-var) ([err] 1))) "bad arity 3")
|
||||||
(assert (= 1 (try (map) ([err] 1))) "bad arity 4")
|
(assert (= 1 (try (map-var) ([err] 1))) "bad arity 4")
|
||||||
(assert (= 1 (try (not) ([err] 1))) "bad arity 5")
|
(assert (= 1 (try (not-var) ([err] 1))) "bad arity 5")
|
||||||
|
|
||||||
# Assembly test
|
# Assembly test
|
||||||
# Fibonacci sequence, implemented with naive recursion.
|
# Fibonacci sequence, implemented with naive recursion.
|
||||||
@ -113,9 +117,9 @@
|
|||||||
|
|
||||||
(assert (= 1 ({:ok 1} :ok)) "calling struct")
|
(assert (= 1 ({:ok 1} :ok)) "calling struct")
|
||||||
(assert (= 2 (@{:ok 2} :ok)) "calling table")
|
(assert (= 2 (@{:ok 2} :ok)) "calling table")
|
||||||
(assert (= :bad (try (@{:ok 2} :ok :no) ([err] :bad))) "calling table too many arguments")
|
(assert (= :bad (try ((identity @{:ok 2}) :ok :no) ([err] :bad))) "calling table too many arguments")
|
||||||
(assert (= :bad (try (:ok @{:ok 2} :no) ([err] :bad))) "calling keyword too many arguments")
|
(assert (= :bad (try ((identity :ok) @{:ok 2} :no) ([err] :bad))) "calling keyword too many arguments")
|
||||||
(assert (= :oops (try (1 1) ([err] :oops))) "calling number fails")
|
(assert (= :oops (try ((+ 2 -1) 1) ([err] :oops))) "calling number fails")
|
||||||
|
|
||||||
# Method test
|
# Method test
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user