@tishlang/tish-lsp 3.3.0 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tish-lsp +0 -0
- package/crates/tish_compile/src/codegen.rs +759 -70
- package/crates/tish_compile/tests/regr_gba_lowered_call_sites.rs +405 -1
- package/crates/tish_lsp/Cargo.toml +1 -1
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -310,8 +310,14 @@ console.log(R.length)
|
|
|
310
310
|
#[test]
|
|
311
311
|
fn a_module_const_array_is_reachable_from_a_lowered_fn_on_gba() {
|
|
312
312
|
let rust = compile_gba("const_array_gba", CONST_ARRAY_READ);
|
|
313
|
+
// Element type is deliberately NOT pinned here — this test is about REACHABILITY (#594), and an
|
|
314
|
+
// all-integer table is now expected to hoist as `[i32; 8]` on GBA. The representation itself is
|
|
315
|
+
// pinned by `an_integral_module_const_array_is_emitted_as_i32_on_gba`.
|
|
313
316
|
assert!(
|
|
314
|
-
rust.contains("const G_TABLE: [
|
|
317
|
+
rust.contains("const G_TABLE: [i32; 8]")
|
|
318
|
+
|| rust.contains("const G_TABLE: [f64; 8]")
|
|
319
|
+
|| rust.contains("const G_table: [i32; 8]")
|
|
320
|
+
|| rust.contains("const G_table: [f64; 8]"),
|
|
315
321
|
"a module const numeric array should get a top-level `const` on GBA (#594):\n{}",
|
|
316
322
|
rust.lines().filter(|l| l.contains("TABLE")).take(4).collect::<Vec<_>>().join("\n")
|
|
317
323
|
);
|
|
@@ -485,3 +491,401 @@ console.log(R.length)
|
|
|
485
491
|
offenders.join("\n ")
|
|
486
492
|
);
|
|
487
493
|
}
|
|
494
|
+
|
|
495
|
+
// ── #48/#52-follow-up: the module-const-array hoist was blind to closures ─────────────────────────
|
|
496
|
+
//
|
|
497
|
+
// `collect_module_const_f64_arrays` hoists a module-level numeric array to a top-level
|
|
498
|
+
// `const G_X: [f64; N]` so a LOWERED fn can reach it (that reachability is the whole point of #594).
|
|
499
|
+
// Admission is supposed to reject any array that is written, or used as a value/member, because a
|
|
500
|
+
// `const` is neither mutable nor addressable. The admission walker checks all of that — but its
|
|
501
|
+
// expression arm had no `ArrowFunction` case, so every use inside a CLOSURE fell into `_ => {}` and
|
|
502
|
+
// was invisible. The sibling scalar pass (`walk_expr_for_global_disqualifiers`) has always had that
|
|
503
|
+
// arm; this walker is a copy that lost it.
|
|
504
|
+
//
|
|
505
|
+
// Net effect once the pass was enabled on GBA: an array that a closure writes got hoisted anyway.
|
|
506
|
+
// The read side renamed to `G_X`, the write side kept the boxed local, and the boxed local is
|
|
507
|
+
// declared *after* the closure — so rustc reports `cannot find value` on a name the game never
|
|
508
|
+
// misspelled. Reported against card-gba (queensblood/triad/gwent) and reproduced independently by
|
|
509
|
+
// tish-gba `examples/hyrule` (`rangLive`, weapons.tish).
|
|
510
|
+
//
|
|
511
|
+
// The trigger needs all three ingredients: a module numeric array, a typed top-level fn that reads
|
|
512
|
+
// it (that is what nominates it for hoisting), and a write from inside a closure.
|
|
513
|
+
|
|
514
|
+
/// The reported repro, reduced. Before the fix this emitted `const G_A: [f64; 3]` plus a write
|
|
515
|
+
/// through a bare `A`, and rustc rejected it with E0425.
|
|
516
|
+
const CLOSURE_WRITE: &str = r#"
|
|
517
|
+
let A: i32[] = [1, 2, 3]
|
|
518
|
+
|
|
519
|
+
function readA(i: i32): i32 { return A[i] }
|
|
520
|
+
|
|
521
|
+
export function run(): i32 {
|
|
522
|
+
let bump = () => { A[0] = A[0] + 1 }
|
|
523
|
+
bump()
|
|
524
|
+
return readA(0)
|
|
525
|
+
}
|
|
526
|
+
"#;
|
|
527
|
+
|
|
528
|
+
#[test]
|
|
529
|
+
fn module_array_written_in_closure_is_not_hoisted_to_const() {
|
|
530
|
+
let rust = compile_gba("closure_write", CLOSURE_WRITE);
|
|
531
|
+
// The array is written, so it must stay a runtime binding: no const hoist at all. Renaming the
|
|
532
|
+
// write site would not be enough — a `const` cannot be assigned through.
|
|
533
|
+
assert!(
|
|
534
|
+
!rust.contains("const G_A"),
|
|
535
|
+
"a module array written from a closure must not be hoisted to a const:\n{}",
|
|
536
|
+
rust
|
|
537
|
+
);
|
|
538
|
+
// And the generated program must not reference a hoisted name it never declared.
|
|
539
|
+
assert!(
|
|
540
|
+
!rust.contains("G_A["),
|
|
541
|
+
"no read may be renamed to a hoist that does not exist:\n{}",
|
|
542
|
+
rust
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/// The second missed case from the same report: `.length` lowers to `.len()`, which is a member use
|
|
547
|
+
/// the walker already rejects — it was just unreachable inside a closure. A hoist here emitted
|
|
548
|
+
/// `A.len()` against a name not in scope.
|
|
549
|
+
const CLOSURE_LENGTH: &str = r#"
|
|
550
|
+
let A: i32[] = [1, 2, 3]
|
|
551
|
+
|
|
552
|
+
function readA(i: i32): i32 { return A[i] }
|
|
553
|
+
|
|
554
|
+
export function run(): i32 {
|
|
555
|
+
let count = () => { return A.length }
|
|
556
|
+
return readA(0) + count()
|
|
557
|
+
}
|
|
558
|
+
"#;
|
|
559
|
+
|
|
560
|
+
#[test]
|
|
561
|
+
fn module_array_member_used_in_closure_is_not_hoisted_to_const() {
|
|
562
|
+
let rust = compile_gba("closure_length", CLOSURE_LENGTH);
|
|
563
|
+
assert!(
|
|
564
|
+
!rust.contains("const G_A"),
|
|
565
|
+
"a module array whose `.length` a closure reads must not be hoisted:\n{}",
|
|
566
|
+
rust
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/// The win must survive: a read-only array indexed from a closure is still a legal hoist, because
|
|
571
|
+
/// the Index arm deliberately does not count `X[i]` as a disqualifying use of `X`. Without this the
|
|
572
|
+
/// fix would be a blanket "any closure mention disables hoisting" and would give back #594.
|
|
573
|
+
const CLOSURE_READ_ONLY: &str = r#"
|
|
574
|
+
let A: i32[] = [1, 2, 3]
|
|
575
|
+
|
|
576
|
+
function readA(i: i32): i32 { return A[i] }
|
|
577
|
+
|
|
578
|
+
export function run(): i32 {
|
|
579
|
+
let pick = () => { return A[1] }
|
|
580
|
+
return readA(0) + pick()
|
|
581
|
+
}
|
|
582
|
+
"#;
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn read_only_module_array_still_hoists_when_a_closure_indexes_it() {
|
|
586
|
+
let rust = compile_gba("closure_read_only", CLOSURE_READ_ONLY);
|
|
587
|
+
assert!(
|
|
588
|
+
rust.contains("G_A"),
|
|
589
|
+
"a read-only module array must still hoist — that reachability is #594:\n{}",
|
|
590
|
+
rust
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ── GBA representation: the hoist must not carry the host's f64 onto a CPU with no FPU ───────────
|
|
595
|
+
//
|
|
596
|
+
// ARM7TDMI has no hardware float. Hoisting `let A: i32[] = [1,2,3]` to `const G_A: [f64; 3]` cost
|
|
597
|
+
// 8 bytes per element instead of 4, an `i32 -> f64 -> usize` round trip on every index, and an
|
|
598
|
+
// `f64::NAN` out-of-range sentinel — while DISCARDING the element type the source had already
|
|
599
|
+
// declared. These pin the integer representation.
|
|
600
|
+
|
|
601
|
+
#[test]
|
|
602
|
+
fn an_integral_module_const_array_is_emitted_as_i32_on_gba() {
|
|
603
|
+
let rust = compile_gba("repr_int", CLOSURE_READ_ONLY);
|
|
604
|
+
assert!(
|
|
605
|
+
rust.contains("const G_A: [i32; 3]"),
|
|
606
|
+
"an all-integer module const array must hoist as [i32; N], not [f64; N]:\n{}",
|
|
607
|
+
rust
|
|
608
|
+
);
|
|
609
|
+
assert!(
|
|
610
|
+
!rust.contains("const G_A: [f64; 3]"),
|
|
611
|
+
"the f64 representation must be gone:\n{}",
|
|
612
|
+
rust
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/// Non-integral values still need f64 — the narrowing is value-driven, not blanket.
|
|
617
|
+
const FRACTIONAL: &str = r#"
|
|
618
|
+
let P: number[] = [0.25, 0.5, 0.25]
|
|
619
|
+
|
|
620
|
+
function readP(i: i32): number { return P[i] }
|
|
621
|
+
|
|
622
|
+
export function run(): number {
|
|
623
|
+
let pick = () => { return P[1] }
|
|
624
|
+
return readP(0) + pick()
|
|
625
|
+
}
|
|
626
|
+
"#;
|
|
627
|
+
|
|
628
|
+
#[test]
|
|
629
|
+
fn a_fractional_module_const_array_stays_f64() {
|
|
630
|
+
let rust = compile_gba("repr_frac", FRACTIONAL);
|
|
631
|
+
assert!(
|
|
632
|
+
rust.contains("const G_P: [f64; 3]"),
|
|
633
|
+
"a fractional array must keep f64 — narrowing it would change its values:\n{}",
|
|
634
|
+
rust
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/// The out-of-range read still has to answer NaN. There is no `i32` meaning "absent", so the
|
|
639
|
+
/// bounds-checked form widens rather than inventing a sentinel that would read as a real element.
|
|
640
|
+
#[test]
|
|
641
|
+
fn an_unproven_index_still_answers_nan() {
|
|
642
|
+
let rust = compile_gba("repr_int", CLOSURE_READ_ONLY);
|
|
643
|
+
if rust.contains("f64::NAN") {
|
|
644
|
+
assert!(
|
|
645
|
+
rust.contains("as f64"),
|
|
646
|
+
"an i32-backed out-of-range read must widen before yielding NaN:\n{}",
|
|
647
|
+
rust
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/// The SECOND hole, found only because the first fix left queensblood at 4 errors instead of 12: the
|
|
653
|
+
/// statement walker visited loop and branch BODIES but not their CONDITIONS. `.length` is a member
|
|
654
|
+
/// use the walker already rejects — it was simply never reached in `while (i < X.length)`, which is
|
|
655
|
+
/// how card-gba's `SB_FORCE`/`SB_PRE`/`TN_REWARD` (exported `i32[]` in qb-catalog.tish, consumed by
|
|
656
|
+
/// qb-engine.tish:567 and main.tish:297) stayed eligible and hoisted.
|
|
657
|
+
const COND_LENGTH: &str = r#"
|
|
658
|
+
let A: i32[] = [1, 2, 3]
|
|
659
|
+
|
|
660
|
+
function readA(i: i32): i32 { return A[i] }
|
|
661
|
+
|
|
662
|
+
export function run(): i32 {
|
|
663
|
+
let i: i32 = 0
|
|
664
|
+
let n: i32 = 0
|
|
665
|
+
while (i < A.length) { n = n + readA(i); i = i + 1 }
|
|
666
|
+
return n
|
|
667
|
+
}
|
|
668
|
+
"#;
|
|
669
|
+
|
|
670
|
+
#[test]
|
|
671
|
+
fn a_use_in_a_loop_condition_disqualifies_the_hoist() {
|
|
672
|
+
let rust = compile_gba("cond_length", COND_LENGTH);
|
|
673
|
+
assert!(
|
|
674
|
+
!rust.contains("const G_A"),
|
|
675
|
+
"`.length` in a while-condition is a member use and must block the hoist:\n{}",
|
|
676
|
+
rust
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/// Same shape, in an `if` condition — that arm had the identical omission.
|
|
681
|
+
const IF_COND: &str = r#"
|
|
682
|
+
let A: i32[] = [1, 2, 3]
|
|
683
|
+
|
|
684
|
+
function readA(i: i32): i32 { return A[i] }
|
|
685
|
+
|
|
686
|
+
export function run(): i32 {
|
|
687
|
+
if (A.length > 2) { return readA(0) }
|
|
688
|
+
return 0
|
|
689
|
+
}
|
|
690
|
+
"#;
|
|
691
|
+
|
|
692
|
+
#[test]
|
|
693
|
+
fn a_use_in_an_if_condition_disqualifies_the_hoist() {
|
|
694
|
+
let rust = compile_gba("if_cond", IF_COND);
|
|
695
|
+
assert!(
|
|
696
|
+
!rust.contains("const G_A"),
|
|
697
|
+
"`.length` in an if-condition must block the hoist:\n{}",
|
|
698
|
+
rust
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ── #594 residual, reported from tish-gba examples/hyrule ─────────────────────────────────────────
|
|
703
|
+
//
|
|
704
|
+
// Reported shape: a fn with NO PARAMS and NO RETURN that WRITES a module array, and IS CALLED. On
|
|
705
|
+
// 3.3.0 that emitted the new `vm_read(&CELL)` module-scope capability without bringing the cell into
|
|
706
|
+
// scope. Two facts from the report narrow it and are both encoded below:
|
|
707
|
+
// - the identical shape with NO callers compiles, because it is dead-code eliminated, so a repro
|
|
708
|
+
// MUST contain a call site;
|
|
709
|
+
// - inlining the body at the call site is a complete workaround.
|
|
710
|
+
//
|
|
711
|
+
// This is the `rangDone()` shape in hyrule's weapons.tish (`export function rangDone() { rangLive[0] = 0 }`),
|
|
712
|
+
// which is the same `rangLive` E0425 that started this investigation.
|
|
713
|
+
|
|
714
|
+
/// No params, no return, writes a module array, and — critically — is called.
|
|
715
|
+
const WRITER_WITH_CALLER: &str = r#"
|
|
716
|
+
let A: i32[] = [0, 0, 0]
|
|
717
|
+
|
|
718
|
+
function readA(i: i32): i32 { return A[i] }
|
|
719
|
+
|
|
720
|
+
function clearA() { A[0] = 0 }
|
|
721
|
+
|
|
722
|
+
export function run(): i32 {
|
|
723
|
+
clearA()
|
|
724
|
+
return readA(0)
|
|
725
|
+
}
|
|
726
|
+
"#;
|
|
727
|
+
|
|
728
|
+
#[test]
|
|
729
|
+
fn a_called_no_param_no_return_module_array_writer_compiles() {
|
|
730
|
+
let rust = compile_gba("writer_with_caller", WRITER_WITH_CALLER);
|
|
731
|
+
// The array is written, so it must NOT have been hoisted…
|
|
732
|
+
assert!(
|
|
733
|
+
!rust.contains("const G_A"),
|
|
734
|
+
"a written module array must not be hoisted:\n{}",
|
|
735
|
+
rust
|
|
736
|
+
);
|
|
737
|
+
// …and no lowered free fn may reference the boxed cell, which lives in `run()`. A free `fn`
|
|
738
|
+
// mentioning `A` is precisely the E0425 the report describes.
|
|
739
|
+
for (i, line) in rust.lines().enumerate() {
|
|
740
|
+
let t = line.trim_start();
|
|
741
|
+
if t.starts_with("fn ") || t.starts_with("pub fn ") {
|
|
742
|
+
assert!(
|
|
743
|
+
!t.contains("clearA") || t.contains("_cell"),
|
|
744
|
+
"clearA must not be emitted as a free fn that cannot see module scope (line {}): {}",
|
|
745
|
+
i + 1,
|
|
746
|
+
t
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ── #600: `let s: string = strArr[i]` moves out of the Vec ────────────────────────────────────────
|
|
753
|
+
const STR_ELEM_BIND: &str = r#"
|
|
754
|
+
let NAMES: string[] = ["alpha", "beta"]
|
|
755
|
+
|
|
756
|
+
export function show(i: i32): string {
|
|
757
|
+
let name: string = NAMES[i]
|
|
758
|
+
return name
|
|
759
|
+
}
|
|
760
|
+
"#;
|
|
761
|
+
|
|
762
|
+
#[test]
|
|
763
|
+
fn binding_a_string_element_clones_instead_of_moving() {
|
|
764
|
+
let rust = compile_gba("str_elem_bind", STR_ELEM_BIND);
|
|
765
|
+
let bad = rust.lines().any(|l| {
|
|
766
|
+
let t = l.trim();
|
|
767
|
+
t.contains("= NAMES[") && !t.contains(".clone()") && !t.contains("&NAMES[")
|
|
768
|
+
});
|
|
769
|
+
assert!(
|
|
770
|
+
!bad,
|
|
771
|
+
"a string element read must clone — a bare index is a move out of the Vec (E0507):\n{}",
|
|
772
|
+
rust.lines().filter(|l| l.contains("NAMES[")).take(4).collect::<Vec<_>>().join("\n")
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ── #599: a written module-level `let x: i32` ────────────────────────────────────────────────────
|
|
777
|
+
const MODULE_SCALAR_WRITE: &str = r#"
|
|
778
|
+
let counter: i32 = 0
|
|
779
|
+
|
|
780
|
+
function bump() { counter = counter + 1 }
|
|
781
|
+
|
|
782
|
+
export function run(): i32 {
|
|
783
|
+
bump()
|
|
784
|
+
return counter
|
|
785
|
+
}
|
|
786
|
+
"#;
|
|
787
|
+
|
|
788
|
+
#[test]
|
|
789
|
+
fn a_written_module_scalar_reads_and_writes_through_the_same_cell() {
|
|
790
|
+
let rust = compile_gba("module_scalar_write", MODULE_SCALAR_WRITE);
|
|
791
|
+
// If reads go through a cell, writes must too. A bare `counter = <i32>` against a VmRef<i32>
|
|
792
|
+
// is E0308 — the two halves disagreeing is the bug.
|
|
793
|
+
for line in rust.lines() {
|
|
794
|
+
let t = line.trim();
|
|
795
|
+
if t.starts_with("counter = ") && !t.contains("vm_write") {
|
|
796
|
+
panic!(
|
|
797
|
+
"write must match the read's representation (#599):\n{}",
|
|
798
|
+
rust.lines().filter(|l| l.contains("counter")).take(6).collect::<Vec<_>>().join("\n")
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ── #597: a typed array passed to a function is COPIED, not aliased ───────────────────────────────
|
|
805
|
+
//
|
|
806
|
+
// `let reqs: i32[] = []` lowers to `Vec<i32>`, but a call site marshals a FRESH `Value::Array` from
|
|
807
|
+
// its elements. The callee's pushes land in the copy and the caller's array stays empty — silently,
|
|
808
|
+
// with no error at compile time or run time. An UNTYPED array passes the same `VmRef` and aliases
|
|
809
|
+
// correctly, so adding an annotation changes program behaviour rather than just representation.
|
|
810
|
+
//
|
|
811
|
+
// The fix cannot live at the call site: building a `Value::Array` out of a `Vec<i32>` is a copy by
|
|
812
|
+
// construction. An array that escapes into a boxed call has to stay boxed.
|
|
813
|
+
const ARRAY_ESCAPES: &str = r#"
|
|
814
|
+
function fill(out) {
|
|
815
|
+
out.push(1)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
export function run(): i32 {
|
|
819
|
+
let reqs: i32[] = []
|
|
820
|
+
fill(reqs)
|
|
821
|
+
return reqs.length
|
|
822
|
+
}
|
|
823
|
+
"#;
|
|
824
|
+
|
|
825
|
+
#[test]
|
|
826
|
+
fn a_typed_array_passed_to_a_function_is_not_copied() {
|
|
827
|
+
let rust = compile_gba("array_escapes", ARRAY_ESCAPES);
|
|
828
|
+
let copied = rust.lines().find(|l| {
|
|
829
|
+
l.contains("Value::Array(VmRef::new(") && l.contains("reqs") && l.contains(".iter()")
|
|
830
|
+
});
|
|
831
|
+
assert!(
|
|
832
|
+
copied.is_none(),
|
|
833
|
+
"a typed array passed to a fn was marshalled as a fresh copy — the callee's writes are \
|
|
834
|
+
silently lost (#597):\n {}",
|
|
835
|
+
copied.unwrap_or("")
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// ── #629: module bindings are emitted in SOURCE ORDER — a fn referencing a LATER binding breaks ───
|
|
840
|
+
//
|
|
841
|
+
// The variable is ORDER, nothing else. Module-level bindings are emitted into `run()` where they
|
|
842
|
+
// appear, but the function values that capture them are emitted at their own source position, so a
|
|
843
|
+
// function declared ABOVE its binding captures a name that is not in scope yet:
|
|
844
|
+
//
|
|
845
|
+
// let rangDone = { Value::native(move |args| { … vm_read(&rangLive) … }) }; // use
|
|
846
|
+
// let rangLive = VmRef::new(vec![(0_f64) as i32]); // declaration
|
|
847
|
+
//
|
|
848
|
+
// This is legal tish — the function is not CALLED before the binding is initialised.
|
|
849
|
+
//
|
|
850
|
+
// The fixture pinned in 57d6aa1be for this got it wrong: it declared `let A` FIRST, so it passed
|
|
851
|
+
// while the bug was live and proved nothing. Moving the declaration is the whole difference, which
|
|
852
|
+
// is why the earlier reduction "reproduced" on neither compiler. Hits readers and writers alike.
|
|
853
|
+
const FORWARD_REF: &str = r#"
|
|
854
|
+
function readA(i: i32): i32 { return A[i] }
|
|
855
|
+
function clearA() { A[0] = 0 }
|
|
856
|
+
|
|
857
|
+
let A: i32[] = [0, 0, 0]
|
|
858
|
+
|
|
859
|
+
export function run(): i32 {
|
|
860
|
+
clearA()
|
|
861
|
+
return readA(0)
|
|
862
|
+
}
|
|
863
|
+
"#;
|
|
864
|
+
|
|
865
|
+
#[test]
|
|
866
|
+
fn a_fn_may_reference_a_module_binding_declared_after_it() {
|
|
867
|
+
let rust = compile_gba("forward_ref", FORWARD_REF);
|
|
868
|
+
// Find where `A` is DECLARED and where it is first USED. A use above the declaration is the bug.
|
|
869
|
+
let decl = rust
|
|
870
|
+
.lines()
|
|
871
|
+
.position(|l| {
|
|
872
|
+
let t = l.trim();
|
|
873
|
+
t.starts_with("let A = ") || t.starts_with("let A:") || t.starts_with("let mut A")
|
|
874
|
+
});
|
|
875
|
+
let first_use = rust.lines().position(|l| {
|
|
876
|
+
let t = l.trim();
|
|
877
|
+
(t.contains("vm_read(&A)") || t.contains("(&A)") || t.contains("A.borrow"))
|
|
878
|
+
&& !t.starts_with("let A")
|
|
879
|
+
});
|
|
880
|
+
if let (Some(d), Some(u)) = (decl, first_use) {
|
|
881
|
+
assert!(
|
|
882
|
+
u > d,
|
|
883
|
+
"#629: `A` is used at line {} but only declared at line {} — use before declaration \
|
|
884
|
+
(E0425). Module bindings must be emitted ahead of the function values that capture \
|
|
885
|
+
them.\n{}",
|
|
886
|
+
u + 1,
|
|
887
|
+
d + 1,
|
|
888
|
+
rust.lines().skip(d.saturating_sub(6)).take(12).collect::<Vec<_>>().join("\n")
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|