Skip to content

Commit 7ab7ddc

Browse files
authored
fix(compiler)!: Disallow multiple provides of the same value (#1689)
1 parent 97c7ce4 commit 7ab7ddc

File tree

6 files changed

+266
-36
lines changed

6 files changed

+266
-36
lines changed

compiler/src/parsing/well_formedness.re

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ type wferr =
1818
| LoopControlOutsideLoop(string, Location.t)
1919
| ReturnStatementOutsideFunction(Location.t)
2020
| MismatchedReturnStyles(Location.t)
21-
| LocalIncludeStatement(Location.t);
21+
| LocalIncludeStatement(Location.t)
22+
| ProvidedMultipleTimes(string, Location.t);
2223

2324
exception Error(wferr);
2425

@@ -78,6 +79,12 @@ let prepare_error =
7879
~loc,
7980
"`include` statements may only appear at the file level.",
8081
)
82+
| ProvidedMultipleTimes(name, loc) =>
83+
errorf(
84+
~loc,
85+
"%s was provided multiple times, but can only be provided once.",
86+
name,
87+
)
8188
)
8289
);
8390

@@ -557,6 +564,194 @@ let no_local_include = (errs, super) => {
557564
};
558565
};
559566

567+
type provided_multiple_times_ctx = {
568+
modules: Hashtbl.t(string, unit),
569+
types: Hashtbl.t(string, unit),
570+
values: Hashtbl.t(string, unit),
571+
};
572+
573+
let provided_multiple_times = (errs, super) => {
574+
let rec extract_bindings = (binds, pattern) =>
575+
switch (pattern.ppat_desc) {
576+
| PPatAny => binds
577+
| PPatVar(bind) => [bind, ...binds]
578+
| PPatTuple(pats)
579+
| PPatArray(pats) => List.fold_left(extract_bindings, binds, pats)
580+
| PPatRecord(pats, _) =>
581+
List.fold_left(
582+
(binds, (_, pat)) => extract_bindings(binds, pat),
583+
binds,
584+
pats,
585+
)
586+
| PPatConstant(_) => binds
587+
| PPatConstraint(pat, _) => extract_bindings(binds, pat)
588+
| PPatConstruct(_, cstr) =>
589+
switch (cstr) {
590+
| PPatConstrRecord(pats, _) =>
591+
List.fold_left(
592+
(binds, (_, pat)) => extract_bindings(binds, pat),
593+
binds,
594+
pats,
595+
)
596+
| PPatConstrTuple(pats) =>
597+
List.fold_left(extract_bindings, binds, pats)
598+
}
599+
| PPatOr(pat1, pat2) =>
600+
extract_bindings([], pat1) @ extract_bindings(binds, pat2)
601+
| PPatAlias(pat, bind) => extract_bindings([bind, ...binds], pat)
602+
};
603+
604+
let ctx =
605+
ref([
606+
{
607+
modules: Hashtbl.create(64),
608+
types: Hashtbl.create(64),
609+
values: Hashtbl.create(64),
610+
},
611+
]);
612+
613+
let enter_module = (p, d) => {
614+
ctx :=
615+
[
616+
{
617+
modules: Hashtbl.create(64),
618+
types: Hashtbl.create(64),
619+
values: Hashtbl.create(64),
620+
},
621+
...ctx^,
622+
];
623+
super.enter_module(p, d);
624+
};
625+
626+
let leave_module = (p, d) => {
627+
ctx := List.tl(ctx^);
628+
super.leave_module(p, d);
629+
};
630+
631+
let enter_toplevel_stmt = ({ptop_desc: desc} as top) => {
632+
let {values, modules, types} = List.hd(ctx^);
633+
switch (desc) {
634+
| PTopModule(Provided | Abstract, {pmod_name, pmod_loc}) =>
635+
if (Hashtbl.mem(modules, pmod_name.txt)) {
636+
errs := [ProvidedMultipleTimes(pmod_name.txt, pmod_loc), ...errs^];
637+
} else {
638+
Hashtbl.add(modules, pmod_name.txt, ());
639+
}
640+
| PTopForeign(
641+
Provided | Abstract,
642+
{pval_name, pval_name_alias, pval_loc},
643+
)
644+
| PTopPrimitive(
645+
Provided | Abstract,
646+
{pval_name, pval_name_alias, pval_loc},
647+
) =>
648+
let name = Option.value(~default=pval_name, pval_name_alias);
649+
if (Hashtbl.mem(values, name.txt)) {
650+
errs := [ProvidedMultipleTimes(name.txt, pval_loc), ...errs^];
651+
} else {
652+
Hashtbl.add(values, name.txt, ());
653+
};
654+
| PTopData(decls) =>
655+
List.iter(
656+
decl => {
657+
switch (decl) {
658+
| (Provided | Abstract, {pdata_name, pdata_loc}) =>
659+
if (Hashtbl.mem(types, pdata_name.txt)) {
660+
errs :=
661+
[ProvidedMultipleTimes(pdata_name.txt, pdata_loc), ...errs^];
662+
} else {
663+
Hashtbl.add(types, pdata_name.txt, ());
664+
}
665+
| (NotProvided, _) => ()
666+
}
667+
},
668+
decls,
669+
)
670+
| PTopLet(Provided | Abstract, _, _, binds) =>
671+
List.iter(
672+
bind => {
673+
let names = extract_bindings([], bind.pvb_pat);
674+
List.iter(
675+
name =>
676+
if (Hashtbl.mem(values, name.txt)) {
677+
errs := [ProvidedMultipleTimes(name.txt, name.loc), ...errs^];
678+
} else {
679+
Hashtbl.add(values, name.txt, ());
680+
},
681+
names,
682+
);
683+
},
684+
binds,
685+
)
686+
| PTopException(
687+
Provided | Abstract,
688+
{ptyexn_constructor: {pext_name, pext_loc}},
689+
) =>
690+
if (Hashtbl.mem(values, pext_name.txt)) {
691+
errs := [ProvidedMultipleTimes(pext_name.txt, pext_loc), ...errs^];
692+
} else {
693+
Hashtbl.add(values, pext_name.txt, ());
694+
}
695+
| PTopProvide(items) =>
696+
let apply_alias = (name, alias) => {
697+
let old_name = Identifier.string_of_ident(name.txt);
698+
let new_name =
699+
switch (alias) {
700+
| Some(alias) => Identifier.string_of_ident(alias.txt)
701+
| None => old_name
702+
};
703+
(old_name, new_name);
704+
};
705+
List.iter(
706+
item => {
707+
switch (item) {
708+
| PProvideType({name, alias, loc}) =>
709+
let (_, name) = apply_alias(name, alias);
710+
if (Hashtbl.mem(types, name)) {
711+
errs := [ProvidedMultipleTimes(name, loc), ...errs^];
712+
} else {
713+
Hashtbl.add(types, name, ());
714+
};
715+
| PProvideModule({name, alias, loc}) =>
716+
let (_, name) = apply_alias(name, alias);
717+
if (Hashtbl.mem(modules, name)) {
718+
errs := [ProvidedMultipleTimes(name, loc), ...errs^];
719+
} else {
720+
Hashtbl.add(modules, name, ());
721+
};
722+
| PProvideValue({name, alias, loc}) =>
723+
let (_, name) = apply_alias(name, alias);
724+
if (Hashtbl.mem(values, name)) {
725+
errs := [ProvidedMultipleTimes(name, loc), ...errs^];
726+
} else {
727+
Hashtbl.add(values, name, ());
728+
};
729+
}
730+
},
731+
items,
732+
);
733+
| PTopModule(NotProvided, _)
734+
| PTopForeign(NotProvided, _)
735+
| PTopPrimitive(NotProvided, _)
736+
| PTopLet(NotProvided, _, _, _)
737+
| PTopException(NotProvided, _)
738+
| PTopInclude(_)
739+
| PTopExpr(_) => ()
740+
};
741+
super.enter_toplevel_stmt(top);
742+
};
743+
744+
{
745+
errs,
746+
iter_hooks: {
747+
...super,
748+
enter_toplevel_stmt,
749+
enter_module,
750+
leave_module,
751+
},
752+
};
753+
};
754+
560755
let compose_well_formedness = ({errs, iter_hooks}, cur) =>
561756
cur(errs, iter_hooks);
562757

@@ -572,6 +767,7 @@ let well_formedness_checks = [
572767
no_loop_control_statement_outside_of_loop,
573768
malformed_return_statements,
574769
no_local_include,
770+
provided_multiple_times,
575771
];
576772

577773
let well_formedness_checker = () =>

compiler/test/__snapshots__/includes.03ddcd3b.0.snapshot renamed to compiler/test/__snapshots__/provides.2a5f527b.0.snapshot

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
includesinclude_with_export_multiple
1+
providesmultiple_provides_8
22
(module
33
(type $none_=>_none (func))
44
(type $none_=>_i32 (func (result i32)))
55
(type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
6-
(type $i32_=>_i32 (func (param i32) (result i32)))
76
(import \"_genv\" \"mem\" (memory $0 0))
87
(import \"_genv\" \"tbl\" (table $tbl 0 funcref))
98
(import \"_genv\" \"relocBase\" (global $relocBase_0 i32))
109
(import \"_genv\" \"moduleRuntimeId\" (global $moduleRuntimeId_0 i32))
11-
(import \"GRAIN$MODULE$runtime/gc\" \"GRAIN$EXPORT$incRef\" (global $GRAIN$EXPORT$incRef_0 (mut i32)))
12-
(import \"GRAIN$MODULE$sameProvide\" \"GRAIN$EXPORT$foo\" (global $foo_1113 (mut i32)))
13-
(import \"GRAIN$MODULE$runtime/gc\" \"incRef\" (func $incRef_0 (param i32 i32) (result i32)))
14-
(import \"GRAIN$MODULE$sameProvide\" \"foo\" (func $foo_1113 (param i32) (result i32)))
10+
(import \"GRAIN$MODULE$runtime/gc\" \"GRAIN$EXPORT$decRef\" (global $GRAIN$EXPORT$decRef_0 (mut i32)))
11+
(import \"GRAIN$MODULE$runtime/gc\" \"decRef\" (func $decRef_0 (param i32 i32) (result i32)))
12+
(global $foo_1110 (mut i32) (i32.const 0))
1513
(global $GRAIN$TABLE_SIZE i32 (i32.const 0))
1614
(elem $elem (global.get $relocBase_0))
1715
(export \"memory\" (memory $0))
16+
(export \"GRAIN$EXPORT$bar\" (global $foo_1110))
17+
(export \"GRAIN$EXPORT$foo\" (global $foo_1110))
1818
(export \"_gmain\" (func $_gmain))
1919
(export \"_gtype_metadata\" (func $_gtype_metadata))
2020
(export \"_start\" (func $_start))
@@ -36,15 +36,25 @@ includes › include_with_export_multiple
3636
(local $4 f32)
3737
(local $5 f64)
3838
(return
39-
(block $cleanup_locals.2 (result i32)
39+
(block $cleanup_locals.4 (result i32)
4040
(local.set $0
41-
(block $compile_block.1 (result i32)
42-
(call $foo_1113
43-
(call $incRef_0
44-
(global.get $GRAIN$EXPORT$incRef_0)
45-
(global.get $foo_1113)
41+
(block $compile_block.3 (result i32)
42+
(block $compile_store.2
43+
(global.set $foo_1110
44+
(tuple.extract 0
45+
(tuple.make
46+
(i32.const 3)
47+
(call $decRef_0
48+
(global.get $GRAIN$EXPORT$decRef_0)
49+
(global.get $foo_1110)
50+
)
51+
)
52+
)
53+
)
54+
(block $do_backpatches.1
4655
)
4756
)
57+
(i32.const 1879048190)
4858
)
4959
)
5060
(local.get $0)
@@ -56,5 +66,5 @@ includes › include_with_export_multiple
5666
(call $_gmain)
5767
)
5868
)
59-
;; custom section \"cmi\", size 287
69+
;; custom section \"cmi\", size 1303
6070
)

compiler/test/suites/includes.re

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ describe("includes", ({test, testSkip}) => {
2222
"include_all_constructor",
2323
"include \"tlists\" as TLists; from TLists use *; Cons(2, Empty)",
2424
);
25-
assertSnapshot(
26-
"include_with_export_multiple",
27-
"include \"sameProvide\" as SameProvide; from SameProvide use *; foo()",
28-
);
2925
/* use {} tests */
3026
assertSnapshot(
3127
"include_some",

compiler/test/suites/provides.re

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,50 @@ describe("provides", ({test, testSkip}) => {
104104
"provide { foo }",
105105
"Unbound value foo",
106106
);
107+
assertCompileError(
108+
"multiple_provides_1",
109+
"provide let foo = 1; provide let foo = 2",
110+
"provided multiple times",
111+
);
112+
assertCompileError(
113+
"multiple_provides_2",
114+
"provide let foo = 1; provide {foo}",
115+
"provided multiple times",
116+
);
117+
assertCompileError(
118+
"multiple_provides_3",
119+
"provide enum Foo {Foo}; provide enum Foo {Foo}",
120+
"provided multiple times",
121+
);
122+
assertCompileError(
123+
"multiple_provides_4",
124+
"provide enum Foo {Foo}; provide {type Foo}",
125+
"provided multiple times",
126+
);
127+
assertCompileError(
128+
"multiple_provides_5",
129+
"provide module Foo {void}; provide module Foo {void}",
130+
"provided multiple times",
131+
);
132+
assertCompileError(
133+
"multiple_provides_6",
134+
"provide module Foo {void}; provide {Foo}",
135+
"provided multiple times",
136+
);
137+
assertCompileError(
138+
"multiple_provides_7",
139+
"let foo = 1; provide {foo, foo}",
140+
"provided multiple times",
141+
);
142+
assertSnapshot(
143+
"multiple_provides_8",
144+
"let foo = 1; provide {foo, foo as bar}",
145+
);
146+
assertCompileError(
147+
"multiple_provides_9",
148+
"let foo = 1; let bar = 2; provide {foo, foo as bar, bar as foo}",
149+
"provided multiple times",
150+
);
107151

108152
assertSnapshot("let_rec_provide", "provide let rec foo = () => 5");
109153

compiler/test/test-libs/sameProvide.gr

Lines changed: 0 additions & 6 deletions
This file was deleted.

stdlib/runtime/numbers.gr

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ provide let coerceNumberToUnsignedWasmI32 = (x: Number) => {
597597
}
598598

599599
@unsafe
600-
provide let coerceNumberToBigInt = (x: Number) => {
600+
let coerceNumberToBigInt = (x: Number) => {
601601
let x = WasmI32.fromGrain(x)
602602
if (isSimpleNumber(x)) {
603603
BI.makeWrappedInt32(untagSimple(x))
@@ -2521,17 +2521,7 @@ provide let coerceNumberToInt64 = (x: Number) => {
25212521
*/
25222522
@unsafe
25232523
provide let coerceNumberToBigInt = (x: Number) => {
2524-
let x = WasmI32.fromGrain(x)
2525-
let result = if (isBigInt(x)) {
2526-
// avoid extra malloc and prevent x from being freed
2527-
Memory.incRef(x)
2528-
x
2529-
} else {
2530-
// incRef x to reuse it via WasmI32.toGrain
2531-
Memory.incRef(x)
2532-
BI.makeWrappedInt64(coerceNumberToWasmI64(WasmI32.toGrain(x): Number))
2533-
}
2534-
WasmI32.toGrain(result): BigInt
2524+
WasmI32.toGrain(coerceNumberToBigInt(x)): BigInt
25352525
}
25362526

25372527
@unsafe

0 commit comments

Comments
 (0)