Warnings for big copies: a struct with 256+ bytes of plain data as a parameter or in for x in list

- on stderr (finch ir stays the IR), as yellow warnings in editors (@W in the index), once per place
- str, arrays and maps inside a struct don't count: passing it copies only their handles
- none in the compiler, the examples or the tests other than the new one
- docs: the warning instead of "not planned"; what was and wasn't checked for Android

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
Nexoniarz 2026-10-29 12:15:22 +01:00
commit d557b5505e
15 changed files with 210 additions and 18 deletions

View file

@ -151,7 +151,7 @@ Pick the guide that fits you:
- [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs` - [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions - [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions
- [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors - [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors
- [x] Threads (`import thread`) and atomics; C++ files; `--target android` (built, not yet tried on a device) - [x] Threads (`import thread`) and atomics; C++ files; `--target android` (compiles; NDK linking and devices untried)
## Layout ## Layout

View file

@ -151,7 +151,7 @@ Wybierz przewodnik dla siebie:
- [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs` - [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia `#define` - [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia `#define`
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach - [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (budowane, jeszcze nie sprawdzone na urządzeniu) - [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (kompiluje się; linkowanie z NDK i urządzenia niesprawdzone)
## Struktura ## Struktura

View file

@ -327,7 +327,7 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
- Arrays, text and structs free themselves at the end of their block. No `free` needed. - Arrays, text and structs free themselves at the end of their block. No `free` needed.
- `b := a` copies; a call's result moves; `return x` moves. - `b := a` copies; a call's result moves; `return x` moves.
- Passing a struct: `fn f(Body b)` works on its own copy (the caller's stays as it was), `fn f(ptr[Body] b)` changes the caller's (`f(body)` or `f(addr(body))`), a method gets `self` by address, never a copy. - Passing a struct: `fn f(Body b)` works on its own copy (the caller's stays as it was), `fn f(ptr[Body] b)` changes the caller's (`f(body)` or `f(addr(body))`), a method gets `self` by address, never a copy. A struct with 256+ bytes of numbers passed by value or in `for x in list` gets a warning.
- `new(...)` + `free(...)` (often with `defer`) for linked lists, trees, sharing. - `new(...)` + `free(...)` (often with `defer`) for linked lists, trees, sharing.
- C memory: C's own functions (`malloc` / `free`, `fclose`, …). - C memory: C's own functions (`malloc` / `free`, `fclose`, …).

View file

@ -503,6 +503,13 @@ makes the C-callable thunk. `atomic_add/sub/swap` lower to `atomicrmw add/sub/xc
take a `ptr[T]` to any integer type. Finch's arrays, text and maps are not synchronised; sharing one between threads take a `ptr[T]` to any integer type. Finch's arrays, text and maps are not synchronised; sharing one between threads
needs a mutex. needs a mutex.
### Warnings
`warn()` prints `file:line:col: warning: …` to stderr (so `finch ir` stays pure IR) or, with `index`, an `@W` record the
language server publishes with severity 2. One per source position. Two are given: a struct parameter, and the element of
`for x in list`, whose plain data (`byteSize()`: numbers, nested structs, C `[N]T`; `str`/arrays/maps count 0, as only
their handle is copied) is 256 bytes or more.
### Struct arguments by address ### Struct arguments by address
`argWant()` (call arguments only, not assignments): when a parameter is `ptr[S]` and the argument is a plain variable `argWant()` (call arguments only, not assignments): when a parameter is `ptr[S]` and the argument is a plain variable

View file

@ -1134,11 +1134,20 @@ Not there yet:
- Interfaces. - Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII. - Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Checks that stop two threads from changing the same array at once (use a mutex). - Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly. Android is built (`--target android`) but not yet tried on a device. - Windows on ARM64, 32-bit systems, WebAssembly. `--target android` makes code for Android, but linking with the NDK and running on a device are untried.
On purpose, not planned: a warning for every big struct that gets copied. A copy in Finch is always visible in the ### Warnings about big copies
code (`b := a`, passing a struct to a `Body b` parameter), a method never copies `self`, and `ptr[Body]` (or passing
the variable to a `ptr[Body]` parameter) shares instead. A warning that fires on ordinary code would be noise. A struct with 256 bytes or more of plain data (numbers, structs of numbers, C's `[N]T`) is copied byte by byte when it
is a parameter (`fn f(Body b)`) and in `for x in bodies`. Finch says so, and still builds the program:
```
game.fch:27:11: warning: 'b' is a Body (264 bytes), copied on every call to weight; take ptr[Body] to share it instead (callers can still pass the variable as it is)
game.fch:45:5: warning: each Body (264 bytes) is copied into 'x'; for i in 0..bodies.len with bodies[i] works on it in place
```
Text, arrays and maps inside a struct don't count: passing the struct copies only their handles, not what they hold.
`b := a` is not warned about: it says "copy" in the code. A method never copies `self`. Editors show the warnings in yellow.
See the [roadmap in the README](../../README.md#roadmap). See the [roadmap in the README](../../README.md#roadmap).
@ -1283,5 +1292,6 @@ Write the header (`float path_len(float x, float y);`, or let `cbindgen` write i
**Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and **Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new: it is built and checked here, but not SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new and only partly checked: Finch's
yet tried on a device. output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
NDK and running on a device have not been tried yet.

View file

@ -496,6 +496,13 @@ thunk wołany z C. `atomic_add/sub/swap` to `atomicrmw add/sub/xchg … seq_cst`
`ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między `ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między
wątkami wymaga mutexu. wątkami wymaga mutexu.
### Ostrzeżenia
`warn()` wypisuje `plik:linia:kolumna: warning: …` na stderr (więc `finch ir` daje samo IR) albo, w trybie `index`,
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno na miejsce w kodzie. Są dwa: parametr-struktura oraz
element `for x in lista`, gdy ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone struktury, `[N]T` z C; `str`, tablice
i mapy liczą się jako 0, bo kopiowany jest tylko ich uchwyt) mają 256 bajtów lub więcej.
### Struktura jako argument przez adres ### Struktura jako argument przez adres
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`, `argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`,

View file

@ -1138,12 +1138,22 @@ Jeszcze nie ma:
- Interfejsów. - Interfejsów.
- Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII. - Tekstu świadomego Unicode: `.len`, `s[i]` i `upper()` działają na bajtach / ASCII.
- Pilnowania, żeby dwa wątki nie zmieniały tej samej tablicy naraz (do tego mutex). - Pilnowania, żeby dwa wątki nie zmieniały tej samej tablicy naraz (do tego mutex).
- Windowsa na ARM64, systemów 32-bitowych, WebAssembly. Android jest budowany (`--target android`), ale jeszcze nie - Windowsa na ARM64, systemów 32-bitowych, WebAssembly. `--target android` robi kod dla Androida, ale linkowanie
sprawdzony na urządzeniu. z NDK i uruchomienie na urządzeniu nie były jeszcze próbowane.
Celowo nie planowane: ostrzeżenie przy każdym kopiowaniu dużej struktury. Kopię w Finchu zawsze widać w kodzie ### Ostrzeżenia o dużych kopiach
(`b := a`, struktura podana do parametru `Cialo c`), metoda nigdy nie kopiuje `self`, a `ptr[Cialo]` (albo zmienna
podana do parametru `ptr[Cialo]`) współdzieli zamiast kopiować. Ostrzeżenie przy zwykłym kodzie byłoby szumem. Struktura z 256 lub więcej bajtami zwykłych danych (liczby, struktury z liczb, `[N]T` z C) jest kopiowana bajt po bajcie,
gdy jest parametrem (`fn f(Cialo c)`) i w `for x in ciala`. Finch o tym mówi, a program i tak buduje:
```
gra.fch:27:11: warning: 'c' is a Cialo (264 bytes), copied on every call to waga; take ptr[Cialo] to share it instead (callers can still pass the variable as it is)
gra.fch:45:5: warning: each Cialo (264 bytes) is copied into 'x'; for i in 0..ciala.len with ciala[i] works on it in place
```
Teksty, tablice i mapy w strukturze się nie liczą: przekazanie struktury kopiuje tylko ich uchwyty, nie zawartość.
`b := a` nie dostaje ostrzeżenia: w kodzie widać, że to kopia. Metoda nigdy nie kopiuje `self`. Edytory pokazują
ostrzeżenia na żółto.
Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju). Plan rozwoju jest w [README](../../README.pl.md#plan-rozwoju).
@ -1288,5 +1298,6 @@ Napisz nagłówek (`float path_len(float x, float y);` albo niech napisze go `cb
**Android**: `finch build gra.fch --target android --shared` robi `libgra.so` dla 64-bitowego Androida na ARM (API 24 **Android**: `finch build gra.fch --target android --shared` robi `libgra.so` dla 64-bitowego Androida na ARM (API 24
i wyżej) clangiem z NDK, znalezionym przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj go z aplikacji, na przykład i wyżej) clangiem z NDK, znalezionym przez `ANDROID_NDK_HOME` (albo `FINCH_CC`). Wczytaj go z aplikacji, na przykład
przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). To nowość: jest budowane i sprawdzane tutaj, ale przez projekt Androida z SDL3 (`SDL_main` biblioteki to `export fn`). To nowość, sprawdzona tylko częściowo: kod Fincha
jeszcze nie na urządzeniu. kompiluje się dla Androida (dane wątków idą przez emulowane TLS, którego wymaga starszy Android), ale linkowanie z NDK
i uruchomienie na urządzeniu nie były jeszcze próbowane.

View file

@ -327,7 +327,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`. - Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`.
- `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi. - `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi.
- Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (ta u wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia tę u wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy kopię. - Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (ta u wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia tę u wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy kopię. Struktura z 256+ bajtami liczb przekazana przez wartość albo w `for x in lista` dostaje ostrzeżenie.
- `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia. - `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia.
- Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …). - Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …).

View file

@ -5,6 +5,7 @@
- Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`. - Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`.
- Built-ins `min`, `max`, `abs`, `clamp`, `assert` and `atomic_…` highlighted and described on hover. - Built-ins `min`, `max`, `abs`, `clamp`, `assert` and `atomic_…` highlighted and described on hover.
- Format Document (the language server runs `finch fmt` on the open file). - Format Document (the language server runs `finch fmt` on the open file).
- Warnings in yellow: a big struct copied on every call or in `for x in list`.
## 2.4.0 ## 2.4.0

View file

@ -7,6 +7,7 @@ import ast
import docs import docs
import cimport import cimport
import platform import platform
import "stdio.h"
struct Var { struct Var {
str name str name
@ -166,6 +167,7 @@ struct Ref {
struct Gen { struct Gen {
[]ast.Program progs []ast.Program progs
[]str warned // file:line:col of the warnings given
bool trace // each function keeps a frame { prev, name, file, line } in @finch_trace bool trace // each function keeps a frame { prev, name, file, line } in @finch_trace
str frame // the current function's frame ("" in helpers) str frame // the current function's frame ("" in helpers)
str frameLine // where its line goes str frameLine // where its line goes
@ -337,6 +339,58 @@ fn fail(ptr[Gen] g, ast.Node n, str msg) {
exit(1) exit(1)
} }
// A warning: the program is still built. On stderr (finch ir's output stays the IR); an @W record for editors.
fn warn(ptr[Gen] g, ast.Node n, str msg) {
where := g.files[n.file] + ":" + str(n.line) + ":" + str(n.col)
if g.warned.contains(where) {
return // once per place (a generic function is built more than once)
}
g.warned.push(where)
if g.index {
g.idx.push("@W\t" + g.files[n.file] + "\t" + str(n.line) + "\t" + str(n.col) + "\t" + msg)
return
}
fputs(where + ": warning: " + msg + "\n", stderr)
}
// About how many bytes of plain data (numbers, structs of them, C's [N]T) a value has, for the copy warnings
fn byteSize(ptr[Gen] g, ast.Type t) -> int {
k := t.kind
if k == "struct" {
sd := g.structs[findStruct(g, t.module, t.name)]
if sd.isC && sd.csize > 0 {
return sd.csize
}
n := 0
for ft in sd.ftypes {
n += byteSize(g, ft)
}
return n
}
if k == "fixed" && t.elem.len > 0 {
return t.count * byteSize(g, t.elem[0])
}
if k == "str" || k == "array" || k == "map" {
return 0 // only its { ptr, len, cap } is copied when it is passed: not counted
}
if k == "ptr" || k == "enum" || k == "int" || k == "float" {
return 8
}
return bitsOf(t) / 8 + int(bitsOf(t) == 1)
}
// a struct this big passed or looped over by value is copied byte by byte each time
fn bigCopy(ptr[Gen] g, ast.Type t) -> int {
if t.kind != "struct" {
return 0
}
n := byteSize(g, t)
if n >= 256 {
return n
}
return 0
}
fn emit(ptr[Gen] g, str line) { fn emit(ptr[Gen] g, str line) {
// with -g every call in a function that has debug info has to say where it is // with -g every call in a function that has debug info has to say where it is
if g.dbgScope >= 0 && line.contains("call ") { if g.dbgScope >= 0 && line.contains("call ") {
@ -2306,6 +2360,10 @@ fn define(ptr[Gen] g, int si) {
} }
for k in 0..g.cur.params.len { for k in 0..g.cur.params.len {
pt := g.cur.params[k] pt := g.cur.params[k]
big := bigCopy(g, pt)
if big > 0 {
warn(g, f.kids[k], "'" + g.cur.pnames[k] + "' is a " + ast.show(pt) + " (" + str(big) + " bytes), copied on every call to " + f.name + "; take ptr[" + ast.show(pt) + "] to share it instead (callers can still pass the variable as it is)")
}
params.push(llty(g, pt) + " %p" + str(k)) params.push(llty(g, pt) + " %p" + str(k))
v := "%p" + str(k) v := "%p" + str(k)
if owning(g, pt) && !g.cur.borrow[k] { if owning(g, pt) && !g.cur.borrow[k] {
@ -2714,6 +2772,14 @@ fn eachStmt(ptr[Gen] g, ast.Node s) {
et = lt.elem[0] et = lt.elem[0]
} }
copyElems := r.isPlace && owning(g, et) && mutates(g, rootVar(s.kids[0]), s.kids[1]) copyElems := r.isPlace && owning(g, et) && mutates(g, rootVar(s.kids[0]), s.kids[1])
big := bigCopy(g, et)
if big > 0 && s.text.len == 0 {
list := "list"
if s.kids[0].kind == "var" {
list = s.kids[0].name
}
warn(g, s, "each " + ast.show(et) + " (" + str(big) + " bytes) is copied into '" + s.name + "'; for i in 0.." + list + ".len with " + list + "[i] works on it in place")
}
iv := alloca(g, "i64") iv := alloca(g, "i64")
store(g, "i64", "0", iv) store(g, "i64", "0", iv)
cond := newLabel(g, "each.cond") cond := newLabel(g, "each.cond")

View file

@ -83,6 +83,7 @@ struct Analysis {
[]str imports []str imports
[]str importKeys // the module each import is: btn -> ui/button []str importKeys // the module each import is: btn -> ui/button
Diag diag Diag diag
[]Diag warnings // shown in yellow; the program still builds
} }
struct Server { struct Server {
@ -324,6 +325,8 @@ fn parseOutput(str output) -> Analysis {
a.structs[k].types.push(f[4]) a.structs[k].types.push(f[4])
} else if tag == "@E" && f.len >= 7 { } else if tag == "@E" && f.len >= 7 {
a.enums.push(EnumRec(module: f[1], name: f[2], file: f[3], line: intOf(f[4]), col: intOf(f[5]), variants: f[6].split(","))) a.enums.push(EnumRec(module: f[1], name: f[2], file: f[3], line: intOf(f[4]), col: intOf(f[5]), variants: f[6].split(",")))
} else if tag == "@W" && f.len >= 5 {
a.warnings.push(Diag(has: true, file: f[1], line: intOf(f[2]), col: intOf(f[3]), message: f[4]))
} else if tag == "@I" && f.len >= 2 { } else if tag == "@I" && f.len >= 2 {
a.imports.push(f[1]) a.imports.push(f[1])
if f.len >= 3 { if f.len >= 3 {
@ -399,6 +402,27 @@ fn Server.publish(str path, Analysis a) {
list.push(d) list.push(d)
now.push(f) now.push(f)
} }
for w in a.warnings {
if w.file == f {
d := json.new_object()
wl := w.line - 1
if wl < 0 {
wl = 0
}
wc := w.col - 1
if wc < 0 {
wc = 0
}
d.set("range", range(wl, wc, wc + 1))
d.set("severity", num(2))
d.set("source", json.new_text("finch"))
d.set("message", json.new_text(w.message))
list.push(d)
if !now.contains(f) {
now.push(f)
}
}
}
p := json.new_object() p := json.new_object()
p.set("uri", json.new_text(pathToUri(f))) p.set("uri", json.new_text(pathToUri(f)))
p.set("diagnostics", list) p.set("diagnostics", list)

View file

@ -34,7 +34,8 @@ for f in run/*.fch new/*.fch; do
failed=$((failed + 1)); echo "FAIL (build) $f"; head -5 "$tmp/err"; continue failed=$((failed + 1)); echo "FAIL (build) $f"; head -5 "$tmp/err"; continue
fi fi
got=$(run_exe "$exe" "$input") got=$(run_exe "$exe" "$input")
if [ "$got" == "$(tr -d '\r' < "${f%.fch}.out")" ]; then # (warnings come from the build, which is not part of what the program prints)
if [ "$got" == "$(tr -d '\r' < "${f%.fch}.out" | grep -v ': warning: ')" ]; then
pass=$((pass + 1)) pass=$((pass + 1))
else else
failed=$((failed + 1)); echo "FAIL $f"; diff <(echo "$got") "${f%.fch}.out" | head -10 failed=$((failed + 1)); echo "FAIL $f"; diff <(echo "$got") "${f%.fch}.out" | head -10

View file

@ -193,6 +193,15 @@ c.notify("textDocument/didChange", {"textDocument": {"uri": uri2, "version": 4},
diags = c.diagnostics(uri2) diags = c.diagnostics(uri2)
check("error: unhandled failure", diags and "can fail" in diags[0]["message"], diags) check("error: unhandled failure", diags and "can fail" in diags[0]["message"], diags)
# a big struct copied on every call: a warning (yellow), not an error
src4 = "struct V {\n" + "".join(" float f%d\n" % i for i in range(40)) + "}\n\nfn len2(V v) -> float {\n return v.f0\n}\n\nfn main() {\n print(len2(V()))\n}\n"
path4 = os.path.join(d, "big.fch")
open(path4, "w").write(src4)
uri4 = pathlib.Path(path4).resolve().as_uri()
c.notify("textDocument/didOpen", {"textDocument": {"uri": uri4, "languageId": "finch", "version": 1, "text": src4}})
w4 = c.diagnostics(uri4)
check("warning for a big copy", w4 and len(w4) == 1 and w4[0]["severity"] == 2 and "copied on every call" in w4[0]["message"] and w4[0]["range"]["start"]["line"] == 43, w4)
# a module imported under another name: completion after its name # a module imported under another name: completion after its name
src3 = "import path as pt\n\nfn main() {\n print(pt.base(\"a/b\"))\n}\n" src3 = "import path as pt\n\nfn main() {\n print(pt.base(\"a/b\"))\n}\n"
path3 = os.path.join(d, "alias.fch") path3 = os.path.join(d, "alias.fch")

52
tests/new/big_copies.fch Normal file
View file

@ -0,0 +1,52 @@
// a struct of 256 bytes or more copied without the code saying so: a warning (the program still builds)
struct M4 {
float a0
float a1
float a2
float a3
float b0
float b1
float b2
float b3
float c0
float c1
float c2
float c3
float d0
float d1
float d2
float d3
}
struct Body {
M4 world
M4 local
int id
}
fn weight(Body b) -> float { // warned: 264 bytes copied on every call
return b.world.a0 + float(b.id)
}
fn nudge(ptr[Body] b) { // shared: no warning
b.world.a0 += 1.0
}
fn small(M4 m) -> float { // 128 bytes: no warning
return m.a0
}
fn main() {
b := Body(id: 2)
nudge(b)
print(weight(b), small(b.world))
bodies := [b, b]
total := 0.0
for x in bodies { // warned: each element copied into x
total += x.world.a0
}
for i in 0..bodies.len { // in place: no warning
total += bodies[i].world.a0
}
print(total)
}

4
tests/new/big_copies.out Normal file
View file

@ -0,0 +1,4 @@
new/big_copies.fch:27:11: warning: 'b' is a Body (264 bytes), copied on every call to weight; take ptr[Body] to share it instead (callers can still pass the variable as it is)
new/big_copies.fch:45:5: warning: each Body (264 bytes) is copied into 'x'; for i in 0..bodies.len with bodies[i] works on it in place
3 1
4