1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Regression tests: suggestion/hint coverage for 7 zero-coverage diagnostic codes.
//
// Codes exercised: ILO-T017, ILO-P007, ILO-P009, ILO-T003, ILO-T008, ILO-T009, ILO-T020
//
// Each test confirms:
// (a) the correct error code fires
// (b) the suggestion field is non-empty (not the empty string / not missing)
//
// Parser-layer codes (P007, P009) are exercised via the CLI `--parse` path;
// verifier-layer codes (T003, T008, T009, T017, T020) via `--verify`.
// Both share the same code paths across tree/VM/Cranelift since only the
// front-end changes.
use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn stderr_of(args: &[&str]) -> String {
let out = ilo().args(args).output().expect("failed to run ilo");
String::from_utf8_lossy(&out.stderr).into_owned()
}
// ---------------------------------------------------------------------------
// ILO-P007: expected type annotation, got token
// e.g. `f x: > n; x` — colon with no type before the arrow
// ---------------------------------------------------------------------------
#[test]
fn ilo_p007_has_suggestion() {
// `f x:42>n;x` — 42 is not a type
let src = "f x:42>n;x";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-P007"),
"expected ILO-P007, got: {stderr}"
);
assert!(
stderr.contains("valid types")
|| stderr.contains("type annotation")
|| stderr.contains("n, t, b"),
"expected suggestion text in ILO-P007 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-P009: expected expression, got token
// bare semicolon as function body
// ---------------------------------------------------------------------------
#[test]
fn ilo_p009_generic_has_suggestion() {
// `f x:n>n;)` — `)` is not an expression
let src = "f x:n>n;)";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-P009"),
"expected ILO-P009, got: {stderr}"
);
// Should have a suggestion (literal, variable name, or function call hint)
assert!(
stderr.contains("literal")
|| stderr.contains("variable")
|| stderr.contains("expression")
|| stderr.contains("value"),
"expected suggestion text in ILO-P009 output, got: {stderr}"
);
}
#[test]
fn ilo_p009_f_type_no_return_has_suggestion() {
// `f x:F>n;x` — F type with no return type specified
let src = "f x:F>n;x";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-P009"),
"expected ILO-P009 for F with no return type, got: {stderr}"
);
assert!(
stderr.contains("return type") || stderr.contains("F n") || stderr.contains("F "),
"expected F-type suggestion in ILO-P009 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-T003: undefined type (in signature) + ternary branch mismatch
// ---------------------------------------------------------------------------
#[test]
fn ilo_t003_undefined_type_has_suggestion() {
// undefined type 'mytype' in signature (no such type declared)
let src = "f x:mytype>n;42";
let stderr = stderr_of(&[src, "--vm", "f"]);
assert!(
stderr.contains("ILO-T003"),
"expected ILO-T003, got: {stderr}"
);
// The closest-match path fires when types exist; when none, we just get the undefined type msg.
// Any output with ILO-T003 is acceptable — presence check is sufficient for code coverage.
// But if there's a suggestion, it must be non-empty.
if stderr.contains("suggestion:") || stderr.contains("hint:") {
assert!(
!stderr.trim_end().ends_with("suggestion:"),
"suggestion field is empty in ILO-T003 output: {stderr}"
);
}
}
#[test]
fn ilo_t003_ternary_mismatch_has_suggestion() {
// ternary where then=n, else=t
let src = "f x:b>n;?h x 1 \"text\"";
let stderr = stderr_of(&[src, "--vm", "f", "true"]);
assert!(
stderr.contains("ILO-T003"),
"expected ILO-T003 for ternary mismatch, got: {stderr}"
);
assert!(
stderr.contains("same type") || stderr.contains("branches") || stderr.contains("ternary"),
"expected ternary-mismatch suggestion in ILO-T003 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-T008: return type mismatch (fallback path, non n/t swap)
// ---------------------------------------------------------------------------
#[test]
fn ilo_t008_return_mismatch_has_suggestion() {
// f returns b but declares n - hits the fallback _ => Some(...)
let src = "f x:n>n;true";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-T008"),
"expected ILO-T008, got: {stderr}"
);
assert!(
stderr.contains("return") || stderr.contains("annotation") || stderr.contains("change"),
"expected suggestion text in ILO-T008 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-T009: arithmetic type error — destructure non-record
// ---------------------------------------------------------------------------
#[test]
fn ilo_t009_destructure_non_record_has_suggestion() {
// destructuring a number — {a;b}=x where x:n — is a type error
let src = "f x:n>n;{a;b}=x;+a b";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-T009"),
"expected ILO-T009 for destructure, got: {stderr}"
);
assert!(
stderr.contains("record") || stderr.contains("named") || stderr.contains("destructur"),
"expected destructure suggestion in ILO-T009 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-T017: record field type mismatch
// ---------------------------------------------------------------------------
#[test]
fn ilo_t017_field_type_mismatch_has_suggestion() {
// type pt { x:n y:n }; providing text for x (which expects n)
let src = "type pt{x:n;y:n}f>pt;pt x:\"bad\" y:0";
let stderr = stderr_of(&[src, "--vm", "f"]);
assert!(
stderr.contains("ILO-T017"),
"expected ILO-T017, got: {stderr}"
);
assert!(
stderr.contains("provide") || stderr.contains("value for") || stderr.contains("n "),
"expected suggestion in ILO-T017 output, got: {stderr}"
);
}
// ---------------------------------------------------------------------------
// ILO-T020: 'with' on non-record type
// ---------------------------------------------------------------------------
#[test]
fn ilo_t020_with_non_record_has_suggestion() {
// `with` on a number — `x with field:1` where x:n
let src = "f x:n>n;y=x with field:1;y";
let stderr = stderr_of(&[src, "--vm", "f", "1"]);
assert!(
stderr.contains("ILO-T020"),
"expected ILO-T020, got: {stderr}"
);
assert!(
stderr.contains("record") || stderr.contains("named") || stderr.contains("with"),
"expected suggestion in ILO-T020 output, got: {stderr}"
);
}