Skip to content
Merged
96 changes: 86 additions & 10 deletions BUGS-TO-REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ fixture project on a private desktop and the IDE was killed: once for each varia
1, and in two successive sessions on one project for the third row and the growth from 18
copies to 20. The first row was seen when the key had been deleted and the next harness run
recreated it, the second at the end of a `check_examples` run. The
harness records it because its registry tidy (`scripts/lib/tb-registry.mjs`) leaves a list
shorter than 21 entries whenever it removes harness projects, and the next project the user
harness records it because its own runs trip it: every IDE a run starts opens a project, and
a run that began on a list holding one entry ended with seventeen copies of it. The registry
tidy (`scripts/lib/tb-registry.mjs`) now puts the list back as it found it, without the
copies; a list that was short to begin with is left short, and the next project the user
opens then trips this.

---

## Compiler crashes on an `Interface` whose name and base are both angle-bracket placeholders
## Compiler crashes on an `Interface` named by an angle-bracket placeholder that has an `Extends` clause

**Build:** BETA 983 (`twinBASIC_win32.dll+00141F7A`)
**Severity:** crash --- takes the compiler down, three restarts, then the IDE gives up.
Expand All @@ -74,23 +76,59 @@ The IDE's DEBUG CONSOLE reports `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-co
`>>> thread 0004: ParsingFileStart, <that file>`, then `restarting from MEMORY`, three
times over.

**Neither half reproduces it on its own**, which is what makes it worth reporting rather
than shrugging at:
**It takes a placeholder name and an `Extends` clause**, and what the clause names does not
matter:

| source | result |
|---|---|
| `Interface <name>` + `End Interface` | TB5182 Syntax error, no crash |
| `Interface IFoo Extends <base-interface>` + `End Interface` | TB5182 + TB5079 + TB5127, no crash |
| `Interface <name> Extends <base-interface>` + `End Interface` | **crash** |
| `Interface <name> Extends IBase` + `End Interface`, no `IBase` anywhere | **crash** |
| the same, with `Interface IBase` or `Class IBase` declared in another file | **crash** |

So it takes a placeholder in *both* positions. The input is not real code --- it is a
syntax skeleton, the shape `docs/Reference/Attributes.md` uses to show where an attribute
goes --- but a parser meeting nonsense should diagnose it, and this one dereferences
something instead.
This entry used to say that it takes a placeholder in *both* positions; the last two rows,
measured on 2026-09-24 with a project of its own each, say otherwise. The input is not real
code --- it is a syntax skeleton, the shape `docs/Reference/Attributes.md` uses to show
where an attribute goes --- but a parser meeting nonsense should diagnose it, and this one
dereferences something instead.

**Found by** pointing `scripts/check_examples.mjs` at the documentation's own code samples;
the skeleton is one of the 1,124 `tb` fences under `docs/`. A crash in a batch of samples
costs the whole batch its result, which is why that tool bisects on exit code 4.
costs the whole batch its result, which is why that tool isolates the sample on exit code 4.

---

## An `Interface` that extends itself compiles without a diagnostic

**Build:** BETA 983
**Severity:** invalid code accepted --- the same cycle through a class is refused.

This two-line file compiles with no error, warning, hint or info:

```
Interface IA Extends IA
End Interface
```

A cycle through two interfaces is accepted the same way, in one file or split across two:
`Interface IA Extends IB` and `Interface IB Extends IA`. The other kinds of cycle are
diagnosed:

| source | result |
|---|---|
| `Class CA` + `Inherits CA` + `End Class` | TB5127 circular reference |
| `Class CA` inheriting `CB` and `Class CB` inheriting `CA`, two files | TB5127 circular reference, TB5022 failed to import inherited members |
| `Type TA` holding a `TB` and `Type TB` holding a `TA`, two modules | TB5101 unable to finalize User Defined Type, possible circular reference |

So a cycle is checked for classes and UDTs, and not for interfaces. What happens when such a
project is built --- its type library has to describe the cycle --- was not tried.

**Observed** on 2026-09-24 with `tbbuild`, a project of its own for each source: exit 0 and
`0 error(s), 0 warning(s), 0 hint(s), 0 info` for the three interface cases, and the
diagnostics above for the rest. **Found by** looking for a compiler crash that needs two
files, to test `check_examples`' handling of one: a cycle between two files was the likeliest
candidate, and the interface cycle compiled instead of crashing.

---

Expand Down Expand Up @@ -977,3 +1015,41 @@ with `(Of ...)`, which compile and return the larger value.

**Found by** probing round 9's UC-65 answer, whose `Max` uses `>` on a type parameter with
nothing to say which types it accepts.

---

## Text that continues a `Debug.Print` line is escaped twice in the DEBUG CONSOLE

**Build:** BETA 983
**Severity:** cosmetic, but it changes what a program appears to print: `&`, `<` and `>` in
the continued part of a line show as `&amp;`, `&lt;` and `&gt;`.

Two statements in a `[RunAfterBuild]` Sub are the whole reproduction:

```
Debug.Print "A";
Debug.Print "&"
```

The DEBUG CONSOLE shows `A&amp;`. The text that opens the line comes out right ---
`Debug.Print "a < b";` shows `a < b` --- and everything printed after it until the line
ends is escaped twice: after `Debug.Print "C";`, `Debug.Print "D";` and
`Debug.Print "<&>"`, the line reads `CD&lt;&amp;&gt;`.

**What does not reproduce it:** a whole line (`Debug.Print "a < b & c"` shows exactly that),
and the same text in one statement (`Debug.Print "B"; "&"` shows `B&`).

`debugOutputPartial` in `ide/main.js`, which takes all of a program's output, and an
add-in's `PrintText` too, and adds to a line that is still open, passes the new text through
`TEXTtoHTML` twice: once as it builds the text and again as it stores it. When the new
text's colour differs from the line's, the `</span><span class='...'>` it puts in to change
colour goes through the second pass too, so the tags themselves show as text. The colour
comes from the output: a program's plain output is `debugConsoleOutputText`, and a
`PrintText` is `debugConsoleOutputTextYELLOW`. With a line left open in the first, made by
calling `debugOutputPartial` from the page, a `PrintText` from the IDE's own Sample 10 add-in
showed as `</span><span class='debugConsoleOutputTextYELLOW'>Hello there from
WaynesWorldAddIn!`. A program's own open line followed by a `PrintText` was not tried.

**Observed** on 2026-09-24 with `scripts/tbrun.mjs`, which decodes the console's stored
entries once, as the pane renders them. Found while making the add-in harness read text
that the IDE appends to an open console line.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ All help is *very much* appreciated :)
The site is rendered by `tbdocs`, a Node.js static site generator kept in [`builder/`](builder/). You need **Node.js 22+**; a PDF or accessibility run additionally needs Chromium, installed once with `npx puppeteer browsers install chrome` (add `--install-deps` on Linux only).

```
npm ci # once, from the repository root
build.bat # renders _site/, _site-offline/ and _site-pdf/, and link-checks them
serve.bat # localhost:4000 with watch + live reload
check.bat # the gates that read the built site, ending in the accessibility scan
test.bat # the gates that test the toolchain itself
book.bat # renders the PDF book; run build.bat first
examples.bat # compiles the twinBASIC code samples in the pages (Windows + a twinBASIC install)
npm ci # once, from the repository root
build.bat # renders _site/, _site-offline/ and _site-pdf/, and link-checks them
serve.bat # localhost:4000 with watch + live reload
check.bat # the gates that read the built site, ending in the accessibility scan
test.bat # the gates that test the toolchain itself
book.bat # renders the PDF book; run build.bat first
examples.bat # compiles the twinBASIC code samples in the pages (Windows + a twinBASIC install)
addin-test.bat # tests IDE add-ins by operating an IDE (Windows + a twinBASIC install)
```

A clean `build.bat && check.bat` is the bar for "ready to commit"; add `test.bat` when the change touched anything outside `docs/`. Each wrapper names the gates it runs, in order, on [Tools and Scripts](https://docs.twinbasic.com/Documentation/Development/Tools). On Linux or macOS, run the `node` command inside each batch file directly --- they are thin wrappers. `examples.bat` is the exception to both: it drives the twinBASIC IDE, so it is Windows-only and is deliberately outside every gate and outside CI.
A clean `build.bat && check.bat` is the bar for "ready to commit"; add `test.bat` when the change touched anything outside `docs/`. Each wrapper names the gates it runs, in order, on [Tools and Scripts](https://docs.twinbasic.com/Documentation/Development/Tools). On Linux or macOS, run the `node` command inside each batch file directly --- they are thin wrappers. `examples.bat` and `addin-test.bat` are the exceptions to both: they drive the twinBASIC IDE, so they are Windows-only and deliberately outside every gate and outside CI.

Where to read more:

Expand Down
34 changes: 28 additions & 6 deletions WIP.ExamplesBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,32 @@ Two things a batch runner must do that a single-fence runner need not:
the page, the fence and the line within the page. Emitted while generating, not
reconstructed afterwards. The offset differs per slot and the arithmetic must come out the
same for all three, which is a probe.
- **Bisect on a compiler crash.** twinBASIC runs the compiler in-process with user code, so
a bad sample can take it down --- and in a batch that loses all hundred with it. On crash,
split and recurse: O(log n) extra builds, paid only on failure. Verified against the real
case, with the crashing sample isolated out of a batch and the rest of the batch still
reporting.
- **Isolate a compiler crash, starting from the sample it names.** twinBASIC runs the
compiler in-process with user code, so a bad sample can take it down --- and in a batch
that loses all hundred with it. `tbbuild`'s crash report names the file the compiler died
parsing, which for a sample is its generated module, so the unit holding it is built alone
and the rest of the batch without it: two builds, paid only on failure. With no sample
named, split and recurse: O(log n) extra builds. On a page of nine samples with the crash
fixture fifth, halving took 7 builds and 48 s and the named start 3 builds and 22 s, with
the same finding and the other eight still reporting. Until then the name went unread ---
`buildStaged` kept `tbbuild`'s report and nothing looked at it --- so every crash paid for
the whole bisect.
- **A crash that needs two samples used to vanish.** Halving separates any pair by the time
it reaches single samples; both halves then build clean, and every sample in the batch
counted as compiling --- the false clean that the crash check exists to prevent, one level
down. Now, when neither part of a crashing batch crashes on its own (the named sample and
the rest, or two halves), `together` searches for the samples the crash needs: holding one
part fixed, whichever half of the other still crashes with it holds them, and when neither
does, each half is searched with the other held. The members are blamed --- each has its
own result, and none is a pass --- and one finding names them all. **No real crash of that
shape is known**, so its tests are probes against a fake lane whose builds crash on the
sample sets a probe chooses; before the fix, the three probe shapes that need a set came
back with nothing crashed, nothing blamed and no finding. Four two-file candidates were
tried for a real one: inheritance cycles through interfaces, classes and UDTs, and the
fixture's placeholder name with a base declared in the other file. None crashes only as a
pair: the class and UDT cycles are diagnosed, the interface cycle is accepted without a
word, which is queued in [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md), and the placeholder
crashes from its own file.

### A diagnostic that lands in a package's own source

Expand Down Expand Up @@ -463,7 +484,8 @@ implementation.
changing" as suspect on this compiler.**
- **A two-line syntax skeleton crashes the compiler**, and it is in the corpus:
`Interface <name> Extends <base-interface>` / `End Interface`, in
`Reference/Attributes.md`. Neither half crashes alone. Recorded in
`Reference/Attributes.md`. The placeholder name is what does it, given any `Extends`
clause: `Extends IBase` crashes it too. Recorded in
[BUGS-TO-REPORT.md](BUGS-TO-REPORT.md); the classifier now refuses a `<placeholder>` as a
declaration name, so it takes an explicit `slot=` to reach the compiler with one.
- **`project.buildPath` must be an explicit file.** The default `${SourcePath}\Build\...`
Expand Down
Loading
Loading