Compare commits
13
Commits
HEAD
..
e615d6c764
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e615d6c764 | ||
|
|
49ec9345a7 | ||
|
|
18a4a8d30d | ||
|
|
d357caecbe | ||
|
|
356bf0e54d | ||
|
|
6b2dca4423 | ||
|
|
f6da23d32e | ||
|
|
2bc4ce7e48 | ||
|
|
92407dabbe | ||
|
|
9fdc564736 | ||
|
|
351a52e261 | ||
|
|
5d53b16a77 | ||
|
|
1cbdc1e56a |
@@ -0,0 +1,51 @@
|
||||
# Codeberg Push Handoff
|
||||
|
||||
Prepared local commits could not be pushed non-interactively.
|
||||
|
||||
- Updated: `2026-08-30T03:01:41Z`
|
||||
- Local branch at time of this handoff update: `main`, ahead of `origin/main`; run `git rev-parse HEAD` for the exact current local commit, because committing this note necessarily changes the final handoff commit SHA.
|
||||
- Local HEAD before this handoff edit: `49ec9345a7a144437e9ccc3e9fec84556122ca98`
|
||||
- Included prepared feature commit: `1cbdc1e56a9509355643cbc7a079f8f6eed442a3`
|
||||
- Included handoff/docs commit: `5d53b16a779d4d662e9a66197eff8c6cc6418aec`
|
||||
- Included prior handoff refresh commit: `351a52e2614b72a5c49acfa15c1f59f2c4f641bd`
|
||||
- Included prior handoff auth-refresh commit: `9fdc564736011b33c11c08792b35ab30638a7057`
|
||||
- Included auth-blocker commit: `92407dabbec1c8d11ee7651b246ebd452f92920d`
|
||||
- Included handoff-refresh commit: `2bc4ce7e48f59755793e90ff85980ff4482ebd82`
|
||||
- Included handoff-refresh commit: `f6da23d32edd4626f4535104218005b356493e31`
|
||||
- Included handoff-refresh commit: `6b2dca4423b375b7e4a3281a76283dc86b6da872`
|
||||
- Included handoff-refresh commit: `356bf0e54d490d8775c5662ded16f0c0cde1bf94`
|
||||
- Included handoff-refresh commit: `d357caecbe4f2c667be87a1d815cf4720486cd97`
|
||||
- Included handoff-refresh commit: `18a4a8d30ddb40544034d3a805b2bdcc9680dfb3`
|
||||
- Included latest handoff-refresh commit before this update: `49ec9345a7a144437e9ccc3e9fec84556122ca98`
|
||||
- Intended branch: `origin/main`
|
||||
- Remote head observed before this run's push attempt: `40f7881afd766d82d2ccb40fa801d3bd9358c7c2`
|
||||
- Remote head observed after this run's push attempt: `40f7881afd766d82d2ccb40fa801d3bd9358c7c2`
|
||||
- Push command attempted: `GIT_TERMINAL_PROMPT=0 git -c credential.helper= push origin HEAD:refs/heads/main`
|
||||
- Push exit: `128`
|
||||
- Blocker: `CODEBERG_TOKEN` is not present in this cron environment, `$HOME/.netrc` is missing, and HTTPS Git push with prompts disabled failed with `fatal: could not read Username for 'https://codeberg.org': terminal prompts disabled`. The token value was not printed or stored because no token was available. Remote `origin/main` did not advance.
|
||||
|
||||
Fresh validation in this checkout before this handoff note edit:
|
||||
|
||||
```bash
|
||||
gcc -std=c99 src/claro.c -O0 -o claro -lm
|
||||
gcc -std=c99 src/claro.c -O0 -o claro.exe -lm
|
||||
./claro typecheck tests/typecheck_method_unknown_method_bad.claro # expected nonzero diagnostic asserted
|
||||
python3 tools/validate_typecheck_diagnostics.py
|
||||
./claro test
|
||||
./claro validate
|
||||
```
|
||||
|
||||
Observed validation result: focused unknown-method diagnostic matched exactly, `tools/validate_typecheck_diagnostics.py` completed, `./claro test` reported `PASS: 0 failure(s)`, and `./claro validate` reported `Validation passed. Claro v1.18.26 foundation checks are ready for use.`
|
||||
|
||||
Because this handoff note is itself a late workspace edit, rerun the same focused/full validation commands after committing the note before any future push claim.
|
||||
|
||||
Latest auth / push verification commands:
|
||||
|
||||
```bash
|
||||
GIT_TERMINAL_PROMPT=0 git -c credential.helper= push origin HEAD:refs/heads/main
|
||||
GIT_TERMINAL_PROMPT=0 git -c credential.helper= ls-remote origin refs/heads/main
|
||||
```
|
||||
|
||||
Result: push failed before authentication because no non-interactive Codeberg credentials were available; remote `main` remained at `40f7881afd766d82d2ccb40fa801d3bd9358c7c2`, not the local branch head.
|
||||
|
||||
Do not start a new feature until the prepared local commits are pushed to Codeberg or the authentication blocker is resolved.
|
||||
@@ -0,0 +1,281 @@
|
||||
# Handoff to GPT-5.6-Luna — Claro Project Review
|
||||
|
||||
Prepared: 2026-08-17 (by Claude, evidence-based; see "Commands actually run" for what was verified in this session).
|
||||
|
||||
This document distinguishes **VERIFIED** (I ran it in this session, in this checkout, and observed the result) from **CLAIMED** (asserted in existing docs/commit messages/handoff notes, not independently re-verified beyond what's noted).
|
||||
|
||||
---
|
||||
|
||||
## 1. Project purpose and architecture
|
||||
|
||||
**Purpose** (from `README.md`, VERIFIED by reading): Claro is a small, readable scripting language/interpreter aimed at beginners, particularly learners with learning disabilities. It favors plain-text keywords (`SET`, `SAY`, `ASK`, `IF`/`END`, `TEACH`/`DO`) over punctuation-heavy syntax, while layering in optional static typing, objects/classes, a local package/project workflow, and offline-friendly HTTP networking.
|
||||
|
||||
**Architecture** (VERIFIED by reading `src/claro.c` and `Makefile`):
|
||||
- Single-file C99 interpreter: `src/claro.c`, 641 lines (but extremely dense — most "lines" are long semicolon-chained C statements with many statements per physical line, not idiomatic multi-line C). `wc -l` undercounts real logical complexity substantially.
|
||||
- Built with plain `gcc -std=c99 -O0 -o claro src/claro.c -lm` (see `Makefile`). No external dependencies beyond libm and, optionally, `curl` as a subprocess for real HTTP(S).
|
||||
- Two build outputs are committed directly to the repo root: `claro` and `claro.exe` (both tracked in git despite `.gitignore` listing them — see §4 risk).
|
||||
- CLI subcommands dispatch from `main()`: `test`, `validate`, `check`, `typecheck`, `fmt`, `doctor`, `examples`, `repl`, `new`, `run`, `package *`, `ide`, `--version`, `help`.
|
||||
- The static type checker lives almost entirely in one function, `typecheck_file()` at `src/claro.c:550` — a single very long line implementing per-statement dispatch for `SET`, `DO`/`CALL`, and `CHECK TYPE`, including inline object/class field and method resolution. `run_validate()` (`src/claro.c:639`) is the master validation entrypoint invoked by `claro validate`, hardcoding the full list of "good" (must type-check clean) and "bad" (must produce a diagnostic) fixture files.
|
||||
- Object/class support: `NEW Class name` registers a synthetic type `OBJECT:Class` in the type environment (`src/claro.c:555`) and pre-seeds `name.field` types from `HAS` declarations collected by `collect_class_field_type_checks()`. Field/method diagnostics (unknown field, unknown method, use-before-`NEW`) are all resolved through this same type-environment lookup at typecheck time — this is a static, line-by-line heuristic checker, not a full parser/AST-based type system.
|
||||
- Tests: `tests/` holds 93 `.claro` fixture files, of which 33 are `typecheck_*` fixtures (paired `_good`/`_bad` cases) driving both `claro validate` and `tools/validate_typecheck_diagnostics.py`. Non-typecheck tests use `.claro`/`.out` pairs run via `claro test`.
|
||||
- `tools/*.py` are auxiliary Python 3 validation scripts (no pip dependencies observed) for specific feature slices (typecheck diagnostics, concurrency, IDE metadata, LSP helper, package security, version convention, per-RC/version validation).
|
||||
|
||||
**Recent development focus** (VERIFIED via `git log --stat` on the last 6 commits): the last five feature commits (`68104bd` → `1cbdc1e`) are a tightly incremental sequence building out **static diagnostics for simple object fields and methods** — missing-`NEW` detection, unknown-field/unknown-method detection, and wrong-type detection — each commit touching `src/claro.c`, one new `tests/typecheck_*_bad.claro` fixture, `tools/validate_typecheck_diagnostics.py`, and three docs files (`README.md`, `docs/ADVANCED_STATIC_TYPING.md`, `docs/CURRENT_STATUS.md`, `docs/ROADMAP.md`) in lockstep. This is a consistent, disciplined pattern: every behavior change ships with a fixture and a doc update in the same commit.
|
||||
|
||||
---
|
||||
|
||||
## 2. Current branch/status (VERIFIED)
|
||||
|
||||
- Branch: `main`. Working tree is clean except for one untracked file: `CODEBERG_PUSH_HANDOFF.md` (present before this session, left untouched — see §7).
|
||||
- No modifications, commits, or pushes were made in this session.
|
||||
- Three remotes configured: `origin` = Codeberg (`https://codeberg.org/RayPals/Claro.git`), `gitea` = self-hosted Gitea over SSH, `github` = `https://github.com/RayPals/Claro.git`.
|
||||
- After `git fetch` on all three remotes (read-only; no push attempted):
|
||||
- `origin/main` (Codeberg) = `40f7881` ("feat: diagnose method calls before object creation") — **1 commit behind local `HEAD` (`1cbdc1e`)**. This exactly matches the blocker described in `CODEBERG_PUSH_HANDOFF.md` (§7).
|
||||
- `gitea/main` = `1cbdc1e` — **identical to local `HEAD`**, i.e. the Gitea mirror is fully up to date and already has the commit that failed to reach Codeberg.
|
||||
- `github/main` = `68104bd` — **4 commits behind local `HEAD`**, missing all five of the most recent object-field/method diagnostic commits.
|
||||
- So: local `HEAD` is the most advanced ref anywhere. Gitea already has it. Codeberg and GitHub do not.
|
||||
|
||||
---
|
||||
|
||||
## 3. Files / recent changes of interest
|
||||
|
||||
- `src/claro.c:550` — `typecheck_file()`, the whole static-typechecker implementation.
|
||||
- `src/claro.c:639` — `run_validate()`, the hardcoded list of fixtures `claro validate` must pass/fail correctly against.
|
||||
- `tests/typecheck_method_unknown_method_bad.claro`, `tests/typecheck_method_unknown_object_bad.claro`, `tests/typecheck_object_field_unknown_expression_bad.claro`, `tests/typecheck_object_field_unknown_object_bad.claro` — the four newest negative fixtures (last four feature commits).
|
||||
- `tools/validate_typecheck_diagnostics.py` — asserts exact diagnostic text for every `_bad` fixture and "Type check OK" for every `_good` fixture; this is the source of truth for exact error-message wording.
|
||||
- `docs/ADVANCED_STATIC_TYPING.md` and `docs/CURRENT_STATUS.md` — kept in sync with each feature commit; read these together with `docs/ROADMAP.md` §"Near-term cleanup priorities" for the project's own stated next steps.
|
||||
- `CODEBERG_PUSH_HANDOFF.md` (untracked, root) — a prior handoff note describing a failed Codeberg push (see §7). Left unmodified per instructions.
|
||||
- `claro` / `claro.exe` (repo root) — prebuilt binaries, tracked in git even though `.gitignore` lists both names (git tracks files added before the ignore rule existed, or `git add -f` was used at some point — not verified which).
|
||||
|
||||
---
|
||||
|
||||
## 4. Commands actually run this session, and real results (all VERIFIED)
|
||||
|
||||
All builds were done to `/tmp` to avoid touching the tracked binaries; no source files were modified.
|
||||
|
||||
```
|
||||
$ gcc -std=c99 -O0 src/claro.c -o /tmp/claro_build -lm
|
||||
```
|
||||
→ Exit 0. Three `-Wformat-truncation` warnings in `create_package_folder()` (snprintf buffer-size warnings on `path[512]`/`readme[512]`), no errors.
|
||||
|
||||
```
|
||||
$ cmp /tmp/claro_build ./claro
|
||||
```
|
||||
→ **Identical.** The tracked `./claro` binary is byte-for-byte reproducible from `src/claro.c` at current `HEAD` with this exact compiler/flags — the repo is not carrying a stale or hand-edited binary.
|
||||
|
||||
```
|
||||
$ ./claro --version
|
||||
```
|
||||
→ `Claro v1.18.26` — matches `README.md` and `CHANGELOG.md`.
|
||||
|
||||
```
|
||||
$ ./claro test
|
||||
```
|
||||
→ `PASS: 0 failure(s)` (all fixture `.claro`/`.out` pairs matched).
|
||||
|
||||
```
|
||||
$ ./claro validate
|
||||
```
|
||||
→ Ran doctor + all `claro test` fixtures + lesson/example `claro check` + full `typecheck_*` good/bad matrix. Ends with `Validation passed. Claro v1.18.26 foundation checks are ready for use.`
|
||||
|
||||
```
|
||||
$ python3 tools/validate_typecheck_diagnostics.py
|
||||
```
|
||||
→ Exit 0, `Typecheck diagnostics validation complete`. Every asserted diagnostic string matched exactly, including the two newest fixtures (`typecheck_method_unknown_method_bad.claro`, `typecheck_method_unknown_object_bad.claro`).
|
||||
|
||||
```
|
||||
$ ./claro doctor
|
||||
```
|
||||
→ All checks `OK`, `Claro folder looks ready.`
|
||||
|
||||
```
|
||||
$ ./claro check examples/quiz.claro
|
||||
$ make check
|
||||
```
|
||||
→ All `OK` (quiz.claro, lessons/01_hello.claro, lessons/03_variables.claro, lessons/08_functions.claro).
|
||||
|
||||
```
|
||||
$ ./claro typecheck tests/typecheck_method_unknown_method_bad.claro
|
||||
```
|
||||
→ `tests/typecheck_method_unknown_method_bad.claro:11: Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.` (exit 1, as intended for a negative fixture).
|
||||
|
||||
```
|
||||
$ git fetch origin main && git fetch gitea main && git fetch github main
|
||||
```
|
||||
→ All three succeeded (network reachable, read-only). Results in §2.
|
||||
|
||||
```
|
||||
$ git status (before and after all of the above)
|
||||
```
|
||||
→ Unchanged: only the pre-existing untracked `CODEBERG_PUSH_HANDOFF.md`. No stray files were left behind by test/validate runs (e.g. no leftover `packages/`, no `MyProject/`, no `network_demo.json`).
|
||||
|
||||
**Not run:** `claro new`, `claro package *`, `claro repl`, `claro ide`, `HTTP GET`/`HTTP SAVE` against real `http://`/`https://` URLs, `run_tests.bat`/`build.bat`/`build.ps1` (Windows-only), and any `tools/validate_*` script not shown above (e.g. `validate_concurrency.py`, `validate_ide_metadata.py`, `validate_lsp_helper.py`, `validate_package_security.py`, per-RC scripts) — these were not exercised and their status should be treated as unknown, not passing.
|
||||
|
||||
---
|
||||
|
||||
## 5. Current defects / blockers
|
||||
|
||||
1. **Codeberg push blocker (CLAIMED in `CODEBERG_PUSH_HANDOFF.md`, corroborated by this session's `git fetch`)**: local `HEAD` (`1cbdc1e`) cannot reach `origin/main` (Codeberg) via HTTPS; the prior attempt failed with "Credentials are incorrect or have expired" using an askpash script against `CODEBERG_TOKEN`. This session's read-only `git fetch origin main` succeeded (network path to Codeberg is fine), so the issue is specifically push authentication/credential validity, not connectivity. This was **not re-attempted** in this session per instructions (no push).
|
||||
2. **GitHub mirror is stale by 4 commits** (VERIFIED via `git fetch github main`). Not previously flagged in any existing handoff doc — this is new information from this session.
|
||||
3. **Committed build artifacts** (`claro`, `claro.exe`) are tracked in git despite being listed in `.gitignore`. They are currently byte-identical to a fresh build from `HEAD` (verified above), so they aren't stale today, but nothing prevents future drift since nothing in CI enforces binary-vs-source parity — a future push could easily commit source changes without rebuilding, leaving a stale binary silently in the repo.
|
||||
4. **Compiler warnings** (VERIFIED): `-Wformat-truncation` on `create_package_folder()` in `src/claro.c` (buffer-size warnings around `path`/`readme`/`meta` snprintf calls at ~`src/claro.c:587`). Not fatal, not currently causing test failures, but worth resolving since `package init`/`package add` write these files for real projects — a sufficiently long package name plus deep path could theoretically truncate a written manifest path.
|
||||
5. **`typecheck_file()` is a single ~90-statement function on one physical line** (`src/claro.c:550`) mixing lexing, type-environment maintenance, and all diagnostic logic for `SET`/`DO`/`CALL`/`CHECK TYPE`. This is a maintainability risk more than a correctness defect today (all fixtures pass), but each new diagnostic (5 of the last 5 feature commits) adds more branches to this one function/line, compounding review difficulty.
|
||||
6. **CI workflow (`.forgejo/workflows/ci.yml`, VERIFIED by reading) does not run `claro validate` or `tools/validate_typecheck_diagnostics.py`** — it only runs `make`, `./claro test`, three lesson `claro check` calls, and `tools/validate_rc3.py`. So the entire typecheck-diagnostics matrix (33 fixtures) that this session hand-verified passes is **not covered by the repo's own CI**, only by manual/local runs. If Codeberg Actions (or Forgejo Actions) mirrors this workflow, a regression in `typecheck_file()` could land without CI catching it.
|
||||
|
||||
---
|
||||
|
||||
## 6. Risks
|
||||
|
||||
- **Single dense source file with no automated static analysis in CI** (no `-Wall`/`-Wextra` gate visible in `Makefile` or CI; warnings above were only surfaced because I passed `-std=c99 -O0` — the same as `Makefile` — and gcc emitted them anyway to stderr, not because CI checks for them).
|
||||
- **Push-credential blocker means "local main" and "Codeberg main" have diverged for at least 6 days** (commit `1cbdc1e` dated in the repo's commit history vs. the handoff note's `2026-08-16` timestamp) with no CI proof that the unpushed commit is what actually will land — anyone treating Codeberg as the source of truth is looking at stale state.
|
||||
- **Three-remote setup (Codeberg/Gitea/GitHub) with no documented sync policy** found in the repo — `gitea` happens to be current, `github` is stale by design or neglect, unclear which is intended as canonical beyond `origin` = Codeberg per `CODEBERG_PUSH_HANDOFF.md`'s framing.
|
||||
- **No test coverage found for the compiler warnings' code path** (`create_package_folder`) — `claro package add` isn't exercised by `claro test`/`claro validate`, only by `tools/validate_v1_16.py` and `tools/validate_package_security.py`, neither of which was run this session.
|
||||
|
||||
---
|
||||
|
||||
## 7. Existing handoff note — preserved unchanged
|
||||
|
||||
`CODEBERG_PUSH_HANDOFF.md` (untracked, repo root) was read but **not modified**, per instructions. Summary of its claims (CLAIMED, not re-verified beyond the `git fetch` cross-check in §2, which is consistent with it):
|
||||
- Local commit `1cbdc1e` could not be pushed to `origin/main` (Codeberg) due to an HTTPS credential failure with `CODEBERG_TOKEN` via non-interactive askpass.
|
||||
- It lists the same validation commands (`gcc` build, `claro typecheck` on the unknown-method fixture, `validate_typecheck_diagnostics.py`, `claro test`, `claro validate`) as having passed prior to the push attempt — this session independently re-ran all of these and got the same pass results (§4), so that portion of the note is corroborated, not just claimed.
|
||||
- Instructs: "After writing or updating this handoff note, rerun validation before making more source changes or retrying the push." This session did not retry the push (out of scope) and made no source changes.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommended next development task
|
||||
|
||||
Given the state above, in priority order:
|
||||
|
||||
1. **Resolve the Codeberg push credential issue** (human/credential-owner action — outside what an agent should do without explicit authorization: needs a valid `CODEBERG_TOKEN` or interactive re-auth). Until resolved, Codeberg `main` will keep drifting behind Gitea/local.
|
||||
2. **Sync GitHub `main`** — decide whether GitHub is meant to mirror Codeberg 1:1; if so, push the 4 missing commits once credentials/policy are confirmed.
|
||||
3. **Add `claro validate` and `python3 tools/validate_typecheck_diagnostics.py` to `.forgejo/workflows/ci.yml`**, so the 33-fixture typecheck matrix is actually gated in CI, not just run manually. This is a small, low-risk, high-value change.
|
||||
4. **Continue the object/method diagnostic series along its established pattern** (per `docs/ROADMAP.md` "Near-term cleanup priorities" and the `docs/ADVANCED_STATIC_TYPING.md` "Status" section, both CLAIMED as the project's own stated direction): the next natural narrow slice, following the existing good/bad-fixture-pair convention, would be object-field diagnostics for compound assignment (e.g. `SET player.score player.score + 5`) or a return-type check for simple methods — both currently listed as "still needed" in `docs/CURRENT_STATUS.md`. This keeps scope narrow and consistent with the last 5 commits' style (one fixture pair + one diagnostic branch + doc sync per commit).
|
||||
5. **Consider a low-risk refactor of `typecheck_file()`** into named per-statement-kind helper functions (e.g. `typecheck_set_statement`, `typecheck_call_statement`, `typecheck_check_type_statement`) before adding much more branching — purely mechanical extraction, same behavior, verified by the existing fixture suite, to keep the "one more `if` per feature" pattern from becoming unreviewable.
|
||||
|
||||
---
|
||||
|
||||
## 9. Precise review questions for GPT-5.6-Luna
|
||||
|
||||
1. **Push blocker**: Given `origin` (Codeberg) is 1 commit behind and the failure mode was HTTPS credential rejection (not network unreachability — this session's `git fetch origin main` succeeded), do you see evidence of a token scope/expiry issue versus a repo-permission issue? Is there a safer non-interactive re-auth path than the askpash script previously used?
|
||||
2. **Remote policy**: Should GitHub `main` be treated as a required mirror of Codeberg `main`, or is its 4-commit staleness intentional (e.g. GitHub kept as a pre-migration snapshot, given the branch name `backup/github-main-before-codeberg-copy-20260717-185100` visible in `git branch -a`)? This affects whether "sync GitHub" belongs on the task list at all.
|
||||
3. **CI coverage gap**: Do you agree `claro validate` and `tools/validate_typecheck_diagnostics.py` should be added to `.forgejo/workflows/ci.yml`, or is there a reason (e.g. runtime cost, redundancy with `claro test`) they were deliberately left out?
|
||||
4. **`typecheck_file()` density**: Is the one-line-per-statement-kind style in `src/claro.c` (confirmed at `typecheck_file()`, `src/claro.c:550`, and `run_validate()`, `src/claro.c:639`) an intentional project convention (e.g. to minimize diff noise or match some formatting tool), or tech debt worth breaking up now, before the object/method diagnostic series grows further? If intentional, what's the constraint driving it?
|
||||
5. **Committed binaries**: Given `claro`/`claro.exe` are `.gitignore`d yet tracked and currently reproducible byte-for-byte from source (verified this session), should they be removed from tracking (relying on CI/Makefile to build), or is committing them load-bearing for some consumer (e.g. `claro new`/docs referencing a ready-to-run binary without requiring a local `gcc`)?
|
||||
6. **Next diagnostic slice**: Does compound field assignment (`SET player.score player.score + 5`) or method-return-type checking better match the project's actual near-term priority, or is there a different "still needed" item from `docs/CURRENT_STATUS.md` you'd rank higher given the codebase as it stands today?
|
||||
|
||||
---
|
||||
|
||||
## 10. Review addendum — 2026-08-17 (re-verification pass, by Claude)
|
||||
|
||||
A second, independent pass was run in this same checkout shortly after the handoff above was written (handoff file mtime `2026-08-17 00:17:15 UTC`; this addendum's checks ran at `2026-08-17 00:18–00:20 UTC`, i.e. minutes later, same day/session window — not a later-day re-check). Purpose: confirm every claim in §1–§9 still holds before handing off, and record exact commands/output for Luna. **No source files were modified, nothing was committed, pushed, or deleted.** `CODEBERG_PUSH_HANDOFF.md` was read only, left byte-identical.
|
||||
|
||||
### Commands run and results (all VERIFIED, this pass)
|
||||
|
||||
```
|
||||
$ git status
|
||||
On branch main
|
||||
Your branch is ahead of 'origin/main' by 1 commit.
|
||||
Untracked: CODEBERG_PUSH_HANDOFF.md, HANDOFF_GPT56_LUNA.md
|
||||
```
|
||||
→ Same as §2, with `HANDOFF_GPT56_LUNA.md` now also untracked (it did not exist yet when §2 was written).
|
||||
|
||||
```
|
||||
$ git log --oneline -5
|
||||
1cbdc1e feat: diagnose unknown object methods
|
||||
40f7881 feat: diagnose method calls before object creation
|
||||
a3c084b feat: improve unknown object field expression diagnostic
|
||||
c41d255 feat: diagnose object field assignment before NEW
|
||||
68104bd feat: diagnose missing object field type checks
|
||||
```
|
||||
→ Unchanged from §1/§2. `git log -1 --format='%H %ci'` confirms `HEAD` is `1cbdc1e`, authored `2026-08-13 15:30:33 +0000` — no new commits landed between the two passes.
|
||||
|
||||
```
|
||||
$ git fetch origin main && git fetch gitea main && git fetch github main
|
||||
```
|
||||
→ All three succeeded (read-only). Ref comparison:
|
||||
- `origin/main` (Codeberg) = `40f7881` — still 1 commit behind local `HEAD`. **Codeberg push blocker is unchanged/unresolved.**
|
||||
- `gitea/main` = `1cbdc1e` — still identical to local `HEAD`.
|
||||
- `github/main` = `68104bd` — still 4 commits behind local `HEAD`.
|
||||
|
||||
This exactly reproduces §2's numbers. `git branch -a` also still shows `remotes/github/backup/github-main-before-codeberg-copy-20260717-185100`, corroborating §9 Q2's observation that GitHub may be an intentional pre-migration snapshot rather than a required mirror.
|
||||
|
||||
```
|
||||
$ gcc -std=c99 -O0 src/claro.c -o /tmp/claro_build_review -lm
|
||||
```
|
||||
→ Exit 0. Same three `-Wformat-truncation` warnings in `create_package_folder()` (`src/claro.c:587`, on the `path`/`meta`/`readme` `snprintf` calls), verbatim to §4/§5 item 4. No new warnings, no errors.
|
||||
|
||||
```
|
||||
$ cmp /tmp/claro_build_review ./claro
|
||||
```
|
||||
→ **Identical**, confirming the tracked binary is still byte-for-byte reproducible from `HEAD`.
|
||||
|
||||
```
|
||||
$ ./claro --version
|
||||
```
|
||||
→ `Claro v1.18.26` — unchanged.
|
||||
|
||||
```
|
||||
$ ./claro test
|
||||
```
|
||||
→ `PASS: 0 failure(s)`.
|
||||
|
||||
```
|
||||
$ ./claro validate
|
||||
```
|
||||
→ Full doctor + test + example-check + typecheck good/bad matrix, ending `Validation passed. Claro v1.18.26 foundation checks are ready for use.`
|
||||
|
||||
```
|
||||
$ python3 tools/validate_typecheck_diagnostics.py
|
||||
```
|
||||
→ Exit 0, `Typecheck diagnostics validation complete`, same fixture set including the two newest method-diagnostic fixtures.
|
||||
|
||||
```
|
||||
$ ./claro doctor
|
||||
```
|
||||
→ All `OK`, `Claro folder looks ready.`
|
||||
|
||||
```
|
||||
$ ./claro check examples/quiz.claro && make check
|
||||
```
|
||||
→ All four checks (`quiz.claro`, `01_hello.claro`, `03_variables.claro`, `08_functions.claro`) `OK`.
|
||||
|
||||
```
|
||||
$ ./claro typecheck tests/typecheck_method_unknown_method_bad.claro
|
||||
```
|
||||
→ `tests/typecheck_method_unknown_method_bad.claro:11: Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.` (exit 1, as expected).
|
||||
|
||||
```
|
||||
$ cat .forgejo/workflows/ci.yml
|
||||
```
|
||||
→ Confirmed: CI runs `make`, `./claro test`, three lesson `claro check` calls, and `tools/validate_rc3.py` only. It does **not** run `claro validate` or `tools/validate_typecheck_diagnostics.py`. §5 item 6's CI coverage gap is confirmed still present, unchanged.
|
||||
|
||||
```
|
||||
$ cat .gitignore
|
||||
```
|
||||
→ Still lists `claro` and `claro.exe` alongside `*.o`, `*.obj`, `*.out`, `.DS_Store`, `Thumbs.db` — both tracked-yet-ignored binaries remain as described in §3/§5 item 3.
|
||||
|
||||
```
|
||||
$ git status --porcelain | grep '^??'
|
||||
```
|
||||
→ `CODEBERG_PUSH_HANDOFF.md` and `HANDOFF_GPT56_LUNA.md` only, before and after all commands above — no stray files (`packages/`, `MyProject/`, etc.) left behind by any test/validate run.
|
||||
|
||||
Also re-read `docs/CURRENT_STATUS.md` (Static type safety and Objects/classes sections) and `docs/ROADMAP.md` ("Near-term cleanup priorities" and "Complete-platform milestones §1–2"): both still list "broader object field checking beyond simple direct assignments," "method return typing," and "type checking through branches and loops" as still-needed — consistent with, and directly supporting, §8 item 4's recommendation. No doc changes since the original handoff was written.
|
||||
|
||||
### Facts that changed between the two passes
|
||||
|
||||
None. Branch state, remote divergence, binary reproducibility, all test/validate results, CI workflow contents, and `.gitignore` contents are identical to what §1–§9 already documented. The only diff is that `HANDOFF_GPT56_LUNA.md` itself now exists as a second untracked file (it was being written during §2's snapshot).
|
||||
|
||||
### Newly discovered risks (from this pass)
|
||||
|
||||
- None beyond what §5/§6 already capture. One clarification worth flagging explicitly for Luna: **both untracked handoff files (`CODEBERG_PUSH_HANDOFF.md`, `HANDOFF_GPT56_LUNA.md`) currently exist only in this local checkout.** Since they're untracked, they will not survive being lost if this checkout is discarded, and they are not visible on any of the three remotes. If this handoff's contents are meant to persist beyond this machine, they need to be committed (as a deliberate, human-approved action — not done in this session) or copied out manually.
|
||||
|
||||
### Recommended next task for Luna (unchanged from §8, re-confirmed)
|
||||
|
||||
The priority order in §8 still holds exactly as written:
|
||||
1. Resolve the Codeberg push credential issue (human/credential-owner action, still blocking `origin/main` by 1 commit).
|
||||
2. Decide GitHub's intended relationship to Codeberg (mirror vs. intentional pre-migration snapshot) before treating its 4-commit staleness as a bug.
|
||||
3. Add `claro validate` and `tools/validate_typecheck_diagnostics.py` to `.forgejo/workflows/ci.yml` — still a small, low-risk, high-value gap, re-confirmed present this pass.
|
||||
4. Continue the object/method diagnostic series — object-field checking for compound assignment or method-return-type checking are the best-supported next slices per the current `docs/CURRENT_STATUS.md`/`docs/ROADMAP.md` wording, re-read and re-confirmed this pass.
|
||||
5. Consider extracting `typecheck_file()` (`src/claro.c:550`) into named per-statement helpers before the diagnostic series grows further — still purely mechanical, still unattempted.
|
||||
|
||||
No new task was surfaced by this re-verification pass; its purpose was confidence, not discovery. Luna can treat §1–§9 as current as of `2026-08-17`, not stale.
|
||||
@@ -180,6 +180,12 @@ If a learner calls a simple object method before creating the object with `NEW`,
|
||||
Object player is not known yet. Create it with NEW ClassName player before calling player.add.
|
||||
```
|
||||
|
||||
If the object exists but the class does not declare that method, Claro now names the class and suggests where to add the missing `TEACH` block:
|
||||
|
||||
```text
|
||||
Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.
|
||||
```
|
||||
|
||||
For simple object fields created with `NEW Class name`, `claro typecheck` also catches direct wrong-type field assignments such as `SET player.score "ten"` when the class says `HAS score NUMBER`:
|
||||
|
||||
```text
|
||||
|
||||
@@ -146,6 +146,18 @@ Output:
|
||||
Object player is not known yet. Create it with NEW ClassName player before calling player.add.
|
||||
```
|
||||
|
||||
If the object exists but the class does not declare the method, Claro now names the class and suggests where to add the missing `TEACH` block:
|
||||
|
||||
```claro
|
||||
DO player.fly 5
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.
|
||||
```
|
||||
|
||||
## Object field assignment checks
|
||||
|
||||
Claro also has a narrow static diagnostic for direct object-field assignments. If a class declares a typed field and a script creates a simple object with `NEW Class name`, `claro typecheck` remembers the field type:
|
||||
|
||||
@@ -52,7 +52,7 @@ Ready now:
|
||||
- typed variables such as `SET score NUMBER 10`
|
||||
- `TYPE OF` and `CHECK TYPE`
|
||||
- typed list/map checks through `claro typecheck`
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, and `DO object.method ...` arguments, and explain when a `DO object.method ...` call happens before the object is created with `NEW`
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, and `DO object.method ...` arguments, explain when a `DO object.method ...` call happens before the object is created with `NEW`, and catch simple calls to undeclared methods with a class-specific `TEACH` hint
|
||||
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields, plus a plain beginner-facing unknown-field diagnostic when the assigned expression type is not inferable yet
|
||||
|
||||
Still needed:
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
|
||||
|
||||
1. Keep beginner-facing docs current and separate from historical release notes.
|
||||
2. Keep examples aligned with the modern simple syntax (`END`, `DO`, short `SET`, short `ASK`) while documenting older compatibility forms separately.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current object-method parameter validation covers one correct `DO object.method ...` call, one wrong-type diagnostic, and missing-object guidance when `DO player.method ...` appears before `NEW`; object-field validation covers correct NUMBER, TEXT, and YESNO direct `SET object.field value` assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type direct assignments, simple NUMBER/TEXT/YESNO-valued unknown-field diagnostics after `NEW Class object`, and a beginner-facing fallback when the unknown field's assigned expression type is not inferable yet.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current object-method parameter validation covers one correct `DO object.method ...` call, one wrong-type diagnostic, missing-object guidance when `DO player.method ...` appears before `NEW`, and a class-specific unknown-method diagnostic when a learner calls a method the class does not declare; object-field validation covers correct NUMBER, TEXT, and YESNO direct `SET object.field value` assignments, direct `CHECK TYPE` metadata acceptance for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type direct assignments, simple NUMBER/TEXT/YESNO-valued unknown-field diagnostics after `NEW Class object`, and a beginner-facing fallback when the unknown field's assigned expression type is not inferable yet.
|
||||
4. Add small examples for each foundation feature before adding bigger syntax.
|
||||
|
||||
## Complete-platform milestones
|
||||
|
||||
+6
-2
@@ -535,21 +535,25 @@ static const char *container_member_type(const char *type,const char *kind){ con
|
||||
static void parse_set_for_typecheck2(const char *t,char **name,char **type,char **expr){ const char *rest=t+3; const char *to=find_word_ci(rest,"TO"); const char *as=find_word_ci(rest,"AS"); *name=NULL; *type=NULL; *expr=NULL; if(as&&to&&as<to){ *name=substr(rest,as); *type=substr(as+2,to); *expr=xstrdup(to+2); return; } if(to){ char *before=substr(rest,to); const char *pcur=before; char *var=unquote_token(&pcur); char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)) *type=xstrdup(mt); *expr=xstrdup(to+2); free(before); free(var); free(maybe); return; } { const char *pcur=rest; char *var=unquote_token(&pcur); const char *after_var=pcur; char *maybe=unquote_token(&pcur); char *mt=trim_inplace(maybe); *name=xstrdup(trim_inplace(var)); if(claro_is_type_word(mt)){ *type=xstrdup(mt); *expr=xstrdup(trim_inplace((char*)pcur)); } else { *expr=xstrdup(trim_inplace((char*)after_var)); } free(var); free(maybe); } }
|
||||
typedef struct TypeParamCheck { char *func; char *param; char *type; int pos; struct TypeParamCheck *next; } TypeParamCheck;
|
||||
typedef struct TypeFieldCheck { char *cls; char *field; char *type; struct TypeFieldCheck *next; } TypeFieldCheck;
|
||||
typedef struct TypeMethodCheck { char *cls; char *method; struct TypeMethodCheck *next; } TypeMethodCheck;
|
||||
static void add_type_param_check(TypeParamCheck **checks,const char *func,const char *param,const char *type,int pos){ TypeParamCheck *c; if(!func||!*func||!param||!*param||!type||!*type) return; c=(TypeParamCheck*)xmalloc(sizeof(TypeParamCheck)); c->func=xstrdup(func); c->param=xstrdup(param); c->type=xstrdup(type); c->pos=pos; c->next=*checks; *checks=c; }
|
||||
static const char *type_param_check_for(TypeParamCheck *checks,const char *func,int pos,const char **param){ TypeParamCheck *c=checks; while(c){ if(c->pos==pos&&ci_eq(c->func,func)){ if(param) *param=c->param; return c->type; } c=c->next; } return NULL; }
|
||||
static void add_type_field_check(TypeFieldCheck **checks,const char *cls,const char *field,const char *type){ TypeFieldCheck *c; if(!cls||!*cls||!field||!*field||!type||!*type) return; c=(TypeFieldCheck*)xmalloc(sizeof(TypeFieldCheck)); c->cls=xstrdup(cls); c->field=xstrdup(field); c->type=xstrdup(type); c->next=*checks; *checks=c; }
|
||||
static const char *type_field_check_for(TypeFieldCheck *checks,const char *cls,const char *field){ TypeFieldCheck *c=checks; while(c){ if(ci_eq(c->cls,cls)&&ci_eq(c->field,field)) return c->type; c=c->next; } return NULL; }
|
||||
static void add_type_method_check(TypeMethodCheck **checks,const char *cls,const char *method){ TypeMethodCheck *c; if(!cls||!*cls||!method||!*method) return; c=(TypeMethodCheck*)xmalloc(sizeof(TypeMethodCheck)); c->cls=xstrdup(cls); c->method=xstrdup(method); c->next=*checks; *checks=c; }
|
||||
static int type_method_check_for(TypeMethodCheck *checks,const char *cls,const char *method){ TypeMethodCheck *c=checks; while(c){ if(ci_eq(c->cls,cls)&&ci_eq(c->method,method)) return 1; c=c->next; } return 0; }
|
||||
static int param_index_named(char **params,int pcnt,const char *name){ int i; char tmp[256]; char *p; for(i=0;i<pcnt;i++){ strncpy(tmp,params[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; p=trim_inplace(tmp); if(ci_eq(p,name)) return i; } return -1; }
|
||||
static void collect_teach_param_checks_from_body(TypeParamCheck **checks,FILE *f,const char *func_name,char **params,int pcnt){ char line[4096]; while(fgets(line,sizeof(line),f)){ char inner[4096],iup[64],iw[128]; char *it; strncpy(inner,line,sizeof(inner)-1); inner[sizeof(inner)-1]=0; it=trim_inplace(inner); if(is_blank_or_comment(it)) continue; first_word(it,iw,sizeof(iw)); upper_copy(iup,iw,sizeof(iup)); if(!strcmp(iup,"END")||!strcmp(iup,"LEARNED")) break; if(!strcmp(iup,"CHECK")&&starts_ci(it,"CHECK TYPE")){ const char *is=find_word_ci(it,"IS"); if(is){ char *expr=substr(it+10,is); char *need=xstrdup(trim_inplace((char*)is+2)); char *en=trim_inplace(expr); int idx=param_index_named(params,pcnt,en); if(idx>=0) add_type_param_check(checks,func_name,en,need,idx); free(expr); free(need); } } } }
|
||||
static TypeParamCheck *collect_function_param_type_checks(FILE *f){ TypeParamCheck *checks=NULL; char line[4096]; char current_class[128]=""; while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; strncpy(tmp,line,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"CLASS")){ const char *pcur=t+5; char *cls=unquote_token(&pcur); snprintf(current_class,sizeof(current_class),"%s",trim_inplace(cls)); free(cls); continue; } if((!strcmp(up,"END")||!strcmp(up,"ENDCLASS"))&¤t_class[0]){ current_class[0]=0; continue; } if(!strcmp(up,"TEACH")){ char teachline[4096]; char *fname=NULL; char **params=NULL; int pcnt=0; char full_name[256]; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&pcnt); if(current_class[0]) snprintf(full_name,sizeof(full_name),"%s.%s",current_class,fname); else snprintf(full_name,sizeof(full_name),"%s",fname); collect_teach_param_checks_from_body(&checks,f,full_name,params,pcnt); } } rewind(f); return checks; }
|
||||
static TypeFieldCheck *collect_class_field_type_checks(FILE *f){ TypeFieldCheck *checks=NULL; char line[4096]; char current_class[128]=""; while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; strncpy(tmp,line,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"CLASS")){ const char *pcur=t+5; char *cls=unquote_token(&pcur); snprintf(current_class,sizeof(current_class),"%s",trim_inplace(cls)); free(cls); continue; } if((!strcmp(up,"END")||!strcmp(up,"ENDCLASS"))&¤t_class[0]){ current_class[0]=0; continue; } if(current_class[0]&&!strcmp(up,"HAS")){ const char *pcur=t+3; char *field=unquote_token(&pcur); char *type=unquote_token(&pcur); add_type_field_check(&checks,current_class,trim_inplace(field),trim_inplace(type)); free(field); free(type); } } rewind(f); return checks; }
|
||||
static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line[4096]; int line_no=0,errs=0; Var *types=NULL; TypeParamCheck *param_checks=NULL; TypeFieldCheck *field_checks=NULL; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1; } param_checks=collect_function_param_type_checks(f); field_checks=collect_class_field_type_checks(f); type_env_set(&types,"RESULT","ANY"); type_env_set(&types,"LASTERROR","TEXT"); type_env_set(&types,"LASTERRORFILE","TEXT"); type_env_set(&types,"LASTERRORLINE","NUMBER"); type_env_set(&types,"LASTEXIT","NUMBER"); while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; line_no++; strncpy(tmp,line,sizeof(line)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"SET")){ char *name=NULL,*type=NULL,*expr=NULL; const char *got,*old; parse_set_for_typecheck2(t,&name,&type,&expr); if(name&&*trim_inplace(name)){ char *nt=trim_inplace(name); char *tt=type?trim_inplace(type):NULL; got=simple_expr_type(types,expr); old=type_env_get(types,nt); if(tt&&*tt){ if(got&&!type_words_match(tt,got)){ printf("%s:%d: Type mismatch for %s: expected %s, but this value looks like %s.\n",path,line_no,nt,tt,got); errs++; } type_env_set(&types,nt,tt); } else if(old){ if(got&&!type_words_match(old,got)){ if(strchr(nt,'.')) printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,old,got); else printf("%s:%d: Type mismatch for %s: it was first set as %s, but this value looks like %s.\n",path,line_no,nt,old,got); errs++; } } else if(strchr(nt,'.')){ char objname[128],fieldname[128]; char *dot=strchr(nt,'.'); const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-nt),nt); snprintf(fieldname,sizeof(fieldname),"%s",dot+1); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ const char *cls=objtype+7; if(!type_field_check_for(field_checks,cls,fieldname)){ if(got) printf("%s:%d: Object %s has no field %s. Check the field name or add HAS %s %s to the class.\n",path,line_no,cls,fieldname,fieldname,got); else printf("%s:%d: Object %s has no field %s. Check the field name or add the field to the class with the right type.\n",path,line_no,cls,fieldname); errs++; } } else { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before setting %s.\n",path,line_no,objname,objname,nt); errs++; } } else if(got) type_env_set(&types,nt,got); else type_env_set(&types,nt,"ANY"); } free(name); free(type); free(expr); }
|
||||
static TypeMethodCheck *collect_class_method_type_checks(FILE *f){ TypeMethodCheck *checks=NULL; char line[4096]; char current_class[128]=""; while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; strncpy(tmp,line,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"CLASS")){ const char *pcur=t+5; char *cls=unquote_token(&pcur); snprintf(current_class,sizeof(current_class),"%s",trim_inplace(cls)); free(cls); continue; } if((!strcmp(up,"END")||!strcmp(up,"ENDCLASS"))&¤t_class[0]){ current_class[0]=0; continue; } if(current_class[0]&&!strcmp(up,"TEACH")){ char teachline[4096]; char *fname=NULL; char **params=NULL; int pcnt=0; strncpy(teachline,t,sizeof(teachline)-1); teachline[sizeof(teachline)-1]=0; parse_teach_parts(teachline,&fname,¶ms,&pcnt); add_type_method_check(&checks,current_class,trim_inplace(fname)); } } rewind(f); return checks; }
|
||||
static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line[4096]; int line_no=0,errs=0; Var *types=NULL; TypeParamCheck *param_checks=NULL; TypeFieldCheck *field_checks=NULL; TypeMethodCheck *method_checks=NULL; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1; } param_checks=collect_function_param_type_checks(f); field_checks=collect_class_field_type_checks(f); method_checks=collect_class_method_type_checks(f); type_env_set(&types,"RESULT","ANY"); type_env_set(&types,"LASTERROR","TEXT"); type_env_set(&types,"LASTERRORFILE","TEXT"); type_env_set(&types,"LASTERRORLINE","NUMBER"); type_env_set(&types,"LASTEXIT","NUMBER"); while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; line_no++; strncpy(tmp,line,sizeof(line)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"SET")){ char *name=NULL,*type=NULL,*expr=NULL; const char *got,*old; parse_set_for_typecheck2(t,&name,&type,&expr); if(name&&*trim_inplace(name)){ char *nt=trim_inplace(name); char *tt=type?trim_inplace(type):NULL; got=simple_expr_type(types,expr); old=type_env_get(types,nt); if(tt&&*tt){ if(got&&!type_words_match(tt,got)){ printf("%s:%d: Type mismatch for %s: expected %s, but this value looks like %s.\n",path,line_no,nt,tt,got); errs++; } type_env_set(&types,nt,tt); } else if(old){ if(got&&!type_words_match(old,got)){ if(strchr(nt,'.')) printf("%s:%d: Type mismatch for field %s: expected %s, but this value looks like %s.\n",path,line_no,nt,old,got); else printf("%s:%d: Type mismatch for %s: it was first set as %s, but this value looks like %s.\n",path,line_no,nt,old,got); errs++; } } else if(strchr(nt,'.')){ char objname[128],fieldname[128]; char *dot=strchr(nt,'.'); const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-nt),nt); snprintf(fieldname,sizeof(fieldname),"%s",dot+1); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ const char *cls=objtype+7; if(!type_field_check_for(field_checks,cls,fieldname)){ if(got) printf("%s:%d: Object %s has no field %s. Check the field name or add HAS %s %s to the class.\n",path,line_no,cls,fieldname,fieldname,got); else printf("%s:%d: Object %s has no field %s. Check the field name or add the field to the class with the right type.\n",path,line_no,cls,fieldname); errs++; } } else { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before setting %s.\n",path,line_no,objname,objname,nt); errs++; } } else if(got) type_env_set(&types,nt,got); else type_env_set(&types,nt,"ANY"); } free(name); free(type); free(expr); }
|
||||
else if(!strcmp(up,"ADD")){ const char *to=find_word_ci(t,"TO"); if(to){ char *expr=substr(t+3,to); char *name=xstrdup(trim_inplace((char*)to+2)); const char *listtype=type_env_get(types,name); const char *need=container_member_type(listtype,"LIST"); const char *got=simple_expr_type(types,expr); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for list %s: expected %s item, but this value looks like %s.\n",path,line_no,name,need,got); errs++; } free(expr); free(name); } }
|
||||
else if(!strcmp(up,"PUT")){ const char *key=find_word_ci(t,"KEY"), *val=find_word_ci(t,"VALUE"); if(key&&val){ char *name=substr(t+3,key); char *expr=xstrdup(trim_inplace((char*)val+5)); char *nt=trim_inplace(name); const char *maptype=type_env_get(types,nt); const char *need=container_member_type(maptype,"MAP"); const char *got=simple_expr_type(types,expr); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for map %s: expected %s value, but this value looks like %s.\n",path,line_no,nt,need,got); errs++; } free(name); free(expr); } }
|
||||
else if(!strcmp(up,"ASK")){ const char *as=find_word_ci(t,"AS"); char *name=NULL,*type=NULL; if(as){ const char *pcur=as+2; name=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } } else { const char *pcur=t+3; char *prompt=unquote_token(&pcur); free(prompt); name=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } } if(name&&*trim_inplace(name)) type_env_set(&types,trim_inplace(name),type?type:"TEXT"); free(name); free(type); }
|
||||
else if(!strcmp(up,"TYPE")&&starts_ci(t,"TYPE OF")){ char *name=check_var_after_as(t); if(name){ type_env_set(&types,name,"TEXT"); free(name); } }
|
||||
else if(!strcmp(up,"NEW")){ const char *as=find_word_ci(t,"AS"); char *name=NULL; char *cls=NULL; char objtype[256]; if(as){ cls=substr(t+3,as); name=xstrdup(trim_inplace((char*)as+2)); } else { const char *pcur=t+3; cls=unquote_token(&pcur); name=unquote_token(&pcur); } snprintf(objtype,sizeof(objtype),"OBJECT:%s",trim_inplace(cls)); if(name&&*trim_inplace(name)){ TypeFieldCheck *fc; char fullname[256]; char *nt=trim_inplace(name); char *ct=trim_inplace(cls); type_env_set(&types,nt,objtype); for(fc=field_checks;fc;fc=fc->next){ if(ci_eq(fc->cls,ct)){ snprintf(fullname,sizeof(fullname),"%s.%s",nt,fc->field); type_env_set(&types,fullname,fc->type); } } } free(cls); free(name); }
|
||||
else if((!strcmp(up,"DO")&&!find_word_ci(t,"TIMES"))||!strcmp(up,"CALL")){ char *fname=NULL; char **parts=NULL; int ac=0,i,is_method=0; char lookup[256],display[256]; if(!strcmp(up,"DO")){ const char *pcur=t+2; fname=unquote_token(&pcur); if(*trim_inplace((char*)pcur)) ac=split_args(pcur,&parts); } else { char *rest=t+4; const char *with=find_word_ci(rest,"WITH"); fname=with?substr(rest,with):xstrdup(rest); if(with) ac=split_args(with+4,&parts); } snprintf(lookup,sizeof(lookup),"%s",trim_inplace(fname)); snprintf(display,sizeof(display),"%s",lookup); { char *dot=strchr(lookup,'.'); if(dot){ char objname[128]; const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-lookup),lookup); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ char method[128]; snprintf(method,sizeof(method),"%s",dot+1); snprintf(lookup,sizeof(lookup),"%s.%s",objtype+7,method); snprintf(display,sizeof(display),"%s",lookup); is_method=1; }
|
||||
else if((!strcmp(up,"DO")&&!find_word_ci(t,"TIMES"))||!strcmp(up,"CALL")){ char *fname=NULL; char **parts=NULL; int ac=0,i,is_method=0; char lookup[256],display[256]; if(!strcmp(up,"DO")){ const char *pcur=t+2; fname=unquote_token(&pcur); if(*trim_inplace((char*)pcur)) ac=split_args(pcur,&parts); } else { char *rest=t+4; const char *with=find_word_ci(rest,"WITH"); fname=with?substr(rest,with):xstrdup(rest); if(with) ac=split_args(with+4,&parts); } snprintf(lookup,sizeof(lookup),"%s",trim_inplace(fname)); snprintf(display,sizeof(display),"%s",lookup); { char *dot=strchr(lookup,'.'); if(dot){ char objname[128]; const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-lookup),lookup); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")){ char method[128]; const char *cls=objtype+7; snprintf(method,sizeof(method),"%s",dot+1); if(!type_method_check_for(method_checks,cls,method)){ printf("%s:%d: Object %s has no method %s. Check the method name or add TEACH %s inside CLASS %s.\n",path,line_no,cls,method,method,cls); errs++; } snprintf(lookup,sizeof(lookup),"%s.%s",cls,method); snprintf(display,sizeof(display),"%s",lookup); is_method=1; }
|
||||
else if(!strcmp(up,"DO")){ printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before calling %s.\n",path,line_no,objname,objname,lookup); errs++; } } } for(i=0;i<ac;i++){ const char *param=NULL; const char *need=type_param_check_for(param_checks,lookup,i,¶m); const char *got=simple_expr_type(types,parts[i]); if(need&&got&&!type_words_match(need,got)){ printf("%s:%d: Type mismatch for %s %s: parameter %s needs %s, but this argument looks like %s.\n",path,line_no,is_method?"method":"function",display,param?param:"argument",need,got); errs++; } } free(fname); }
|
||||
else if(!strcmp(up,"CHECK")&&starts_ci(t,"CHECK TYPE")){ const char *is=find_word_ci(t,"IS"); if(is){ char *expr=substr(t+10,is); char *need=xstrdup(trim_inplace((char*)is+2)); char *et=trim_inplace(expr); const char *got=simple_expr_type(types,et); if(got&&!type_words_match(need,got)){ printf("%s:%d: Type check failed: expected %s, but %s looks like %s.\n",path,line_no,need,et,got); errs++; } else if(!got&&strchr(et,'.')){ char objname[128],fieldname[128]; char *dot=strchr(et,'.'); const char *objtype; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-et),et); snprintf(fieldname,sizeof(fieldname),"%s",dot+1); objtype=type_env_get(types,objname); if(objtype&&starts_ci(objtype,"OBJECT:")&&!type_field_check_for(field_checks,objtype+7,fieldname)){ printf("%s:%d: Object %s has no field %s. Check the field name or add HAS %s %s to the class.\n",path,line_no,objtype+7,fieldname,fieldname,need); errs++; } else if(!objtype){ printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s before checking %s.\n",path,line_no,objname,objname,et); errs++; } } free(expr); free(need); } else { printf("%s:%d: CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER\n",path,line_no); errs++; } }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
CLASS Player
|
||||
HAS score NUMBER
|
||||
|
||||
TEACH add points
|
||||
CHECK TYPE points IS NUMBER
|
||||
SET score score + points
|
||||
END
|
||||
END
|
||||
|
||||
NEW Player player
|
||||
DO player.fly 5
|
||||
@@ -29,6 +29,9 @@ EXPECTED = {
|
||||
"tests/typecheck_method_unknown_object_bad.claro": [
|
||||
"tests/typecheck_method_unknown_object_bad.claro:10: Object player is not known yet. Create it with NEW ClassName player before calling player.add.",
|
||||
],
|
||||
"tests/typecheck_method_unknown_method_bad.claro": [
|
||||
"tests/typecheck_method_unknown_method_bad.claro:11: Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.",
|
||||
],
|
||||
"tests/typecheck_object_field_bad.claro": [
|
||||
"tests/typecheck_object_field_bad.claro:6: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user