Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7547128
Infer every match arm in the checker.
SeanCheatham Sep 16, 2026
c33ea91
Test literal components in tuple patterns.
SeanCheatham Sep 16, 2026
e8f49ed
Pin Map, Set, and List payload types at construction and use.
SeanCheatham Sep 16, 2026
5c790cb
Revert dead Map, Set, and List kit helpers.
SeanCheatham Sep 16, 2026
da46e36
Skip the coverage no-op call when coverage is off.
SeanCheatham Sep 16, 2026
f7710bf
Pin Queue and Deferred payloads from nested offers.
SeanCheatham Sep 16, 2026
a133601
Reject nested composite patterns at check.
SeanCheatham Sep 16, 2026
6ac270e
Guard application of lambdas with unresolved parameter types.
SeanCheatham Sep 16, 2026
ef08b15
Pin Queue and Deferred payloads through lambda parameters.
SeanCheatham Sep 17, 2026
befd951
Bind lambda parameters from expected function types.
SeanCheatham Sep 17, 2026
7b4aa05
Intern emitted string literals behind a pinned rc.
SeanCheatham Sep 17, 2026
2bc7145
Allow nested constructor patterns so match can test inner tags.
SeanCheatham Sep 17, 2026
445a63d
Pin a generic def's type parameters when checking its body.
SeanCheatham Sep 17, 2026
499fb4c
Pin generic type parameters for every check inside a def body.
SeanCheatham Sep 17, 2026
027ac89
Reject arithmetic on a generic parameter inside its def.
SeanCheatham Sep 17, 2026
27662f0
Reject flatMap, apply, and Resource.make on a generic parameter.
SeanCheatham Sep 17, 2026
9a9eeed
Reject View.each, require, and Property.check on a generic parameter.
SeanCheatham Sep 17, 2026
d193e1e
Reject IO payload unification of a generic parameter with a concrete …
SeanCheatham Sep 17, 2026
7348525
Reject Net.serve and View.each when a generic parameter stands in for…
SeanCheatham Sep 17, 2026
bae7984
Reject kit arguments that use a generic def parameter after substitut…
SeanCheatham Sep 17, 2026
1218830
Add POSIX ERE capture and first-match replace.
SeanCheatham Sep 17, 2026
af9a5cb
Format checker and emit oracles so kernel check can pass.
SeanCheatham Sep 17, 2026
9591288
Wait for the iOS app on quit and stop capping URLSession at 1s.
SeanCheatham Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/embedder-mobile/shells/ios/run_sim.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ PACKAGE="$OUT/package/ios"
MODE="${3:-run}"
CONSOLE=""
BUNDLE=""
APP_NAME=""

app_alive() {
[ -n "$APP_NAME" ] && pgrep -f "/${APP_NAME}.app/${APP_NAME}" >/dev/null
}

stop_app() {
if [ -n "$BUNDLE" ]; then
Expand All @@ -18,7 +23,11 @@ stop_app() {
wait "$CONSOLE" 2>/dev/null || true
CONSOLE=""
fi

n=0
while app_alive && [ "$n" -lt 100 ]; do
n=$((n + 1))
sleep 0.1
done
}

trap stop_app EXIT
Expand All @@ -28,6 +37,7 @@ trap 'exit 143' TERM HUP
launch() {
local name next_bundle
name="$(sed -n 's/^name = "\(.*\)"/\1/p' "$PACKAGE/package.toml")"
APP_NAME="$name"
next_bundle="$(/usr/libexec/PlistBuddy -c Print:CFBundleIdentifier "$PACKAGE/$name.app/Info.plist")"
echo "Install $name on the simulator."
if ! xcrun simctl install "$DEVICE" "$PACKAGE/$name.app"; then
Expand Down
8 changes: 4 additions & 4 deletions crates/embedder-mobile/shells/ios/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ def shows(text):
working = app_pid()
proc.stdin.write("q\n")
proc.stdin.flush()
assert proc.wait(timeout=10) == 0, "quit fails"
wait_for(lambda: dead(working), "quit cleanup", 10)
assert proc.wait(timeout=30) == 0, "quit fails"
wait_for(lambda: dead(working), "quit cleanup", 30)
thread.join(timeout=10)
assert not thread.is_alive(), "console process survives quit"
dump = json.loads((app / "output path" / "debug.json").read_text())
Expand All @@ -152,8 +152,8 @@ def shows(text):
wait_for(lambda: "ios-loop-v3" in lines and launches(lines), "second launch")
working = app_pid()
proc.send_signal(signal.SIGINT)
assert proc.wait(timeout=10) in (-signal.SIGINT, 130), "interrupt status is wrong"
wait_for(lambda: dead(working), "interrupt cleanup", 10)
assert proc.wait(timeout=30) in (-signal.SIGINT, 130), "interrupt status is wrong"
wait_for(lambda: dead(working), "interrupt cleanup", 30)
thread.join(timeout=10)
assert not thread.is_alive(), "console process survives interruption"
subprocess.run([sys.executable, str(Path(__file__).with_name("test_viewport.py")), device], check=True)
Expand Down
8 changes: 8 additions & 0 deletions crates/runtime/include/scuzz_rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ typedef struct SzString {
} SzString;

SzString *sz_string_from_cstr(const char *cstr);
SzString *sz_string_lit(const char *cstr);
SzString *sz_string_from_bytes(const char *bytes, size_t len);
const char *sz_string_cstr(const SzString *s);
void sz_string_free(SzString *s);
Expand Down Expand Up @@ -192,6 +193,13 @@ typedef struct SzMap SzMap;
SzList *sz_string_lines(const SzString *s);
/* Split on non-overlapping `sep`. Empty `sep` copies `s` as one cell. */
SzList *sz_string_split(const SzString *s, const SzString *sep);
/* First match. Cell 0 is the full match. Later cells are groups. Empty
* when there is no match or the pattern is bad. Empty pattern is empty. */
SzList *sz_string_capture(const SzString *s, const SzString *pat);
/* Replace the first match with a literal string. No backreferences. A
* miss, a bad pattern, or an empty pattern copies `s`. */
SzString *sz_string_replace_match(const SzString *s, const SzString *pat,
const SzString *repl);

/* Boxed i64 for IO[Int] */
void *sz_box_i64(int64_t n);
Expand Down
5 changes: 3 additions & 2 deletions crates/runtime/src/net_apple.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ - (void)start {
config.URLCredentialStorage = nil;
config.HTTPShouldSetCookies = NO;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
config.timeoutIntervalForRequest = 1;
config.timeoutIntervalForResource = 5;
/* Idle bounds only. IO.timeout cancels the task through the finalizer. */
config.timeoutIntervalForRequest = 30;
config.timeoutIntervalForResource = 60;
config.waitsForConnectivity = NO;
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
Expand Down
196 changes: 177 additions & 19 deletions crates/runtime/src/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ static SzIo *attempt_drop(SzIo *inner) {
#define SZ_ALLOC_MAGIC 0x535A414Cu /* 'SZAL' */
#define SZ_RC_MAGIC 0x535A5243u /* 'SZRC' */
#define SZ_RC_TOMB 0x535A544Du /* 'SZTM' */
/* Pinned rc: an interned literal that retain and release never touch. */
#define SZ_RC_PINNED UINT32_MAX

typedef struct SzRcHdr {
uint32_t magic;
Expand Down Expand Up @@ -283,6 +285,7 @@ typedef struct CoverageHit {
static CoverageHit *coverage_hits[256];
static char *coverage_path;
static int coverage_probed;
static int coverage_off;

static void coverage_clear(void) {
size_t i;
Expand All @@ -307,8 +310,10 @@ static void coverage_probe(void) {
return;
coverage_probed = 1;
path = getenv("SCUZZ_COVERAGE_DUMP");
if (!path || !*path)
if (!path || !*path) {
coverage_off = 1;
return;
}
coverage_path = malloc(strlen(path) + 1);
if (!coverage_path)
sz_panic("coverage: out of memory");
Expand All @@ -319,6 +324,7 @@ static void coverage_probe(void) {
void sz_coverage_env_refresh(void) {
coverage_clear();
coverage_probed = 0;
coverage_off = 0;
}

static void coverage_hit(const char *loc) {
Expand Down Expand Up @@ -353,13 +359,16 @@ static void coverage_hit(const char *loc) {
}

void sz_coverage_hit(const char *loc) {
if (coverage_off)
return;
coverage_hit(loc);
}

void sz_panic_push_src(const char *loc) {
if (!loc || !loc[0])
return;
coverage_hit(loc);
if (!coverage_off)
coverage_hit(loc);
if (g_panic_src_n < SZ_PANIC_SRC_MAX)
g_panic_src[g_panic_src_n++] = loc;
}
Expand Down Expand Up @@ -469,7 +478,7 @@ uint64_t sz_alloc_rc_sum(void) {
uint64_t n = 0;
SzRcHdr *h;
for (h = g_live; h; h = h->next) {
if (h->magic == SZ_RC_MAGIC)
if (h->magic == SZ_RC_MAGIC && h->rc != SZ_RC_PINNED)
n += h->rc;
}
return n;
Expand Down Expand Up @@ -538,9 +547,13 @@ void *sz_rc_alloc(size_t size, uint32_t kind) {
}

void sz_retain(void *ptr) {
SzRcHdr *h;
if (!sz_is_rc(ptr))
return;
sz_rc_hdr(ptr)->rc += 1;
h = sz_rc_hdr(ptr);
if (h->rc == SZ_RC_PINNED)
return;
h->rc += 1;
}

uint32_t sz_rc_kind(const void *ptr) {
Expand All @@ -563,6 +576,8 @@ void sz_release(void *ptr) {
return;
}
h = sz_rc_hdr(ptr);
if (h->rc == SZ_RC_PINNED)
return;
if (h->rc > 1) {
h->rc -= 1;
return;
Expand Down Expand Up @@ -898,6 +913,44 @@ SzString *sz_string_from_cstr(const char *cstr) {
return sz_string_from_bytes(cstr, strlen(cstr));
}

/* Interned string literals. Emitted code calls this for compile-time
* literals only; runtime data goes through sz_string_from_cstr. The first
* call builds the string and pins its rc, so later evaluations share one
* allocation that retain and release never touch. */
#define SZ_LIT_BUCKETS 4096
typedef struct SzLitEnt {
struct SzLitEnt *next;
SzString *s;
} SzLitEnt;
static SzLitEnt *g_lits[SZ_LIT_BUCKETS];

SzString *sz_string_lit(const char *cstr) {
size_t len;
size_t i;
uint32_t hash;
SzLitEnt *e;
SzString *s;
if (!cstr)
sz_panic("sz_string_lit(null)");
len = strlen(cstr);
hash = 5381;
for (i = 0; i < len; i++)
hash = hash * 33 + (unsigned char)cstr[i];
hash %= SZ_LIT_BUCKETS;
for (e = g_lits[hash]; e; e = e->next)
if (e->s->len == len && !memcmp(e->s->data, cstr, len))
return e->s;
s = sz_string_from_bytes(cstr, len);
sz_rc_hdr(s)->rc = SZ_RC_PINNED;
e = (SzLitEnt *)malloc(sizeof(*e));
if (!e)
sz_panic("out of memory");
e->s = s;
e->next = g_lits[hash];
g_lits[hash] = e;
return s;
}

const char *sz_string_cstr(const SzString *s) {
return s && s->data ? s->data : "";
}
Expand Down Expand Up @@ -1380,32 +1433,53 @@ int64_t sz_string_contains(const SzString *s, const SzString *needle) {
return sz_string_index_of(s, needle) >= 0 ? 1 : 0;
}

enum { SZ_RE_MAX_PAT = 1024, SZ_RE_MAX_MATCH = 32 };

static int sz_re_nul_ok(const SzString *s) {
return s && s->data && strlen(s->data) == s->len;
}

/* 0 compiled, 1 empty pattern, -1 fail. Caller calls regfree when 0. */
static int sz_re_compile(const SzString *pat, regex_t *re) {
if (!pat || !pat->data)
return -1;
if (pat->len > SZ_RE_MAX_PAT)
return -1;
if (strlen(pat->data) != pat->len)
return -1;
if (pat->len == 0)
return 1;
if (regcomp(re, pat->data, REG_EXTENDED) != 0)
return -1;
return 0;
}

static SzString *sz_re_group(const char *text, const regmatch_t *m) {
if (!m || m->rm_so < 0)
return sz_string_from_bytes("", 0);
return sz_string_from_bytes(text + m->rm_so, (size_t)(m->rm_eo - m->rm_so));
}

/* POSIX ERE. Full-string match on UTF-8 bytes. No capture.
* A bad pattern, a NUL in the text, or a pattern over 1024 bytes is 0.
* Empty pattern: match only the empty string. Do not call libc regex for
* that case. Darwin and glibc disagree on empty vs empty. */
int64_t sz_string_matches(const SzString *s, const SzString *pat) {
regex_t re;
regmatch_t m;
const char *text;
const char *p;
int crc;
int rc;

if (!s || !s->data || !pat || !pat->data)
return 0;
if (pat->len > 1024)
return 0;
p = pat->data;
if (strlen(p) != pat->len)
return 0;
text = s->data;
if (strlen(text) != s->len)
crc = sz_re_compile(pat, &re);
if (crc == 1)
return s && s->len == 0 ? 1 : 0;
if (crc != 0)
return 0;
if (pat->len == 0)
return s->len == 0 ? 1 : 0;
if (regcomp(&re, p, REG_EXTENDED) != 0)
if (!sz_re_nul_ok(s)) {
regfree(&re);
return 0;
rc = regexec(&re, text, 1, &m, 0);
}
rc = regexec(&re, s->data, 1, &m, 0);
regfree(&re);
if (rc != 0)
return 0;
Expand All @@ -1414,6 +1488,90 @@ int64_t sz_string_matches(const SzString *s, const SzString *pat) {
return 1;
}

/* First match. Cell 0 is the full match. Later cells are groups.
* Empty list when there is no match or the pattern is bad. Empty pattern
* does not match. */
SzList *sz_string_capture(const SzString *s, const SzString *pat) {
regex_t re;
regmatch_t m[SZ_RE_MAX_MATCH];
size_t nmatch;
size_t i;
int crc;
SzList *acc = NULL;

crc = sz_re_compile(pat, &re);
if (crc != 0)
return NULL;
if (!sz_re_nul_ok(s)) {
regfree(&re);
return NULL;
}
nmatch = re.re_nsub + 1;
if (nmatch > SZ_RE_MAX_MATCH)
nmatch = SZ_RE_MAX_MATCH;
if (regexec(&re, s->data, nmatch, m, 0) != 0) {
regfree(&re);
return NULL;
}
i = nmatch;
while (i > 0) {
SzString *g;
SzList *old;
i--;
g = sz_re_group(s->data, &m[i]);
old = acc;
acc = sz_list_cons(g, old);
sz_release(g);
sz_release(old);
}
regfree(&re);
return acc;
}

/* Replace the first match with a literal string. No backreferences.
* A miss, a bad pattern, an empty pattern, or a NUL in the text copies s. */
SzString *sz_string_replace_match(const SzString *s, const SzString *pat,
const SzString *repl) {
regex_t re;
regmatch_t m;
int crc;
size_t slen;
const char *text;
const char *rp;
size_t nrepl;
size_t out_len;
char *buf;
SzString *out;

slen = s && s->data ? s->len : 0;
text = s && s->data ? s->data : "";
nrepl = repl && repl->data ? repl->len : 0;
rp = repl && repl->data ? repl->data : "";
crc = sz_re_compile(pat, &re);
if (crc != 0)
return sz_string_from_bytes(text, slen);
if (!sz_re_nul_ok(s)) {
regfree(&re);
return sz_string_from_bytes(text, slen);
}
if (regexec(&re, text, 1, &m, 0) != 0 || m.rm_so < 0) {
regfree(&re);
return sz_string_from_bytes(text, slen);
}
out_len = slen - (size_t)(m.rm_eo - m.rm_so) + nrepl;
buf = (char *)sz_alloc(out_len + 1);
memcpy(buf, text, (size_t)m.rm_so);
if (nrepl)
memcpy(buf + (size_t)m.rm_so, rp, nrepl);
memcpy(buf + (size_t)m.rm_so + nrepl, text + (size_t)m.rm_eo,
slen - (size_t)m.rm_eo);
buf[out_len] = '\0';
out = sz_string_from_bytes(buf, out_len);
sz_free(buf);
regfree(&re);
return out;
}

int64_t sz_string_ends_with(const SzString *s, const SzString *suffix) {
size_t slen;
size_t n;
Expand Down
Loading
Loading