Skip to content

Commit c362849

Browse files
authored
pref,cgen: add -no-closures option to detect closure usage earlier (for emscripten or for less well supported platforms) (#25565)
1 parent 6989dc1 commit c362849

10 files changed

Lines changed: 61 additions & 1 deletion

File tree

.github/workflows/other_ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ jobs:
137137

138138
# NB: this does not mean it runs, but at least keeps it from regressing
139139
- name: Ensure V can be compiled with -autofree
140-
run: ./v -autofree -o v2 cmd/v
140+
run: ./v -autofree cmd/v
141+
142+
- name: Ensure V can be compiled with -no-closures
143+
run: ./v -no-closures cmd/v
141144

142145
- name: Shader examples can be built
143146
run: |

vlib/v/compiler_errors_test.v

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ fn test_all() {
9191
global_run_dir := '${checker_dir}/globals_run'
9292
run_dir := '${checker_dir}/run'
9393
skip_unused_dir := 'vlib/v/tests/skip_unused'
94+
no_closures_dir := 'vlib/v/tests/no_closures'
9495

9596
checker_tests := get_tests_in_dir(checker_dir, false).filter(!it.contains('with_check_option'))
9697
parser_tests := get_tests_in_dir(parser_dir, false)
@@ -100,6 +101,7 @@ fn test_all() {
100101
module_tests := get_tests_in_dir(module_dir, true)
101102
run_tests := get_tests_in_dir(run_dir, false)
102103
skip_unused_dir_tests := get_tests_in_dir(skip_unused_dir, false)
104+
no_closures_tests := get_tests_in_dir(no_closures_dir, false)
103105
checker_with_check_option_tests := get_tests_in_dir(checker_with_check_option_dir,
104106
false)
105107
mut tasks := Tasks{
@@ -118,6 +120,7 @@ fn test_all() {
118120
tasks.add('', run_dir, 'run', '.run.out', run_tests, false)
119121
tasks.add('', checker_with_check_option_dir, '-check', '.out', checker_with_check_option_tests,
120122
false)
123+
tasks.add('', no_closures_dir, '-no-closures run', '.out', no_closures_tests, false)
121124
tasks.run()
122125

123126
if os.user_os() == 'linux' {

vlib/v/gen/c/cgen.v

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4465,6 +4465,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
44654465
if name !in g.anon_fns {
44664466
g.anon_fns << name
44674467
g.gen_closure_fn(expr_styp, m, name)
4468+
if g.pref.no_closures {
4469+
g.error('a closure was generated for m.name: ${m.name}', node.pos)
4470+
}
44684471
}
44694472
}
44704473
g.write('builtin__closure__closure_create(${name}, ')

vlib/v/gen/c/fn.v

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
421421
node.is_c_variadic)
422422
if is_closure {
423423
g.nr_closures++
424+
if g.pref.no_closures {
425+
g.error('a closure was generated for function', node.pos)
426+
}
424427
}
425428
arg_str := g.out.after(arg_start_pos)
426429
if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin

vlib/v/help/build/build-c.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ see also `v help build`.
322322
user,gcboehm,eval
323323
user,gg_record_trace,skip
324324

325+
-no-closures
326+
Produce a compile time error early, if V generates a closure. That happens implicitly,
327+
if you try to treat methods as values, or explicitly through `fn [captures] (){}`.
328+
This option is useful to prevent accidental introduction of closures for environments,
329+
that do not support closures well (projects targeting wasm, or projects that have to be
330+
ported for less supported platforms, that do not have implementations for the closure thunks).
331+
Note: the CI for the V compiler, checks that V itself, can be compiled with `-no-closures`,
332+
to ease porting.
333+
325334
-no-rsp
326335
By default, V passes all C compiler options to the backend C compiler
327336
in so called "response files" (https://gcc.gnu.org/wiki/Response_Files).

vlib/v/pref/pref.v

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ pub mut:
188188
bare_builtin_dir string // Set by -bare-builtin-dir xyz/ . The xyz/ module should contain implementations of malloc, memset, etc, that are used by the rest of V's `builtin` module. That option is only useful with -freestanding (i.e. when is_bare is true).
189189
no_preludes bool // Prevents V from generating preludes in resulting .c files
190190
custom_prelude string // Contents of custom V prelude that will be prepended before code in resulting .c files
191+
no_closures bool // Produce a compile time error, if a closure was generated for any reason (an implicit receiver method was stored, or an explicit `fn [captured]()`).
191192
cmain string // The name of the generated C main function. Useful with framework like code, that uses macros to re-define `main`, like SDL2 does. When set, V will always generate `int THE_NAME(int ___argc, char** ___argv){`, *no matter* the platform.
192193
lookup_path []string
193194
output_cross_c bool // true, when the user passed `-os cross` or `-cross`
@@ -809,6 +810,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
809810
res.skip_notes = true
810811
res.notes_are_errors = false
811812
}
813+
'-no-closures' {
814+
res.no_closures = true
815+
}
812816
'-no-rsp' {
813817
res.no_rsp = true
814818
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
vlib/v/tests/no_closures/method_closure.vv:9:15: cgen error: a closure was generated for m.name: member
2+
7 |
3+
8 | fn main() {
4+
9 | x := UInt(4).member // generate an implicit closure that captures the receiver 4
5+
| ~~~~~~
6+
10 | res := x()
7+
11 | assert res == 40
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
type UInt = u32
2+
3+
fn (me UInt) member() u32 {
4+
println('member called')
5+
return me * 10
6+
}
7+
8+
fn main() {
9+
x := UInt(4).member // generate an implicit closure that captures the receiver 4
10+
res := x()
11+
assert res == 40
12+
println('ok')
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
vlib/v/tests/no_closures/simple_closure.vv:3:8: cgen error: a closure was generated for function
2+
1 | fn main() {
3+
2 | my_var := 12
4+
3 | c1 := fn [my_var] () int {
5+
| ~~~~~~~~~~~~~~~~~~~~
6+
4 | return my_var
7+
5 | }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
my_var := 12
3+
c1 := fn [my_var] () int {
4+
return my_var
5+
}
6+
assert c1() == 12
7+
println('ok')
8+
}

0 commit comments

Comments
 (0)