plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
272d6bd
— Peter John
2026-11-06T15:49:17+05:30
Simpler language: no inheritance
- compiler/checker/src/callcheck.rs +1 -7
- compiler/checker/src/fncheck.rs +1 -21
- compiler/checker/src/infer.rs +0 -5
- compiler/checker/src/lib.rs +0 -1
- compiler/checker/src/matchcheck.rs +10 -45
- compiler/checker/src/patterns.rs +0 -9
- compiler/checker/src/state.rs +2 -6
- compiler/checker/src/subtyping.rs +0 -103
- compiler/checker/src/tables.rs +0 -1
- compiler/checker/src/unification.rs +3 -6
- compiler/checker/tests/checker/features/mutability.rs +0 -8
- compiler/checker/tests/checker/main.rs +0 -1
- compiler/checker/tests/checker/question_tests.rs +0 -6
- compiler/checker/tests/checker/subtyping_tests.rs +0 -124
- compiler/checker/tests/fixtures/custom_actor_probe2.plum +0 -58
- compiler/core/src/ast.rs +2 -26
- compiler/core/src/closure_spec.rs +0 -9
- compiler/core/src/ctors.rs +15 -156
- compiler/core/src/drive.rs +7 -21
- compiler/core/src/embeds.rs +0 -517
- compiler/core/src/layout.rs +7 -28
- compiler/core/src/lib.rs +0 -2
- compiler/core/src/lower.rs +2 -6
- compiler/core/src/parser.rs +4 -27
- compiler/core/src/purity.rs +3 -14
- compiler/core/src/subtyping.rs +0 -92
- compiler/core/src/tests_lower.rs +0 -2
- compiler/core/tests/core/closure_spec_test.rs +0 -16
- compiler/core/tests/core/formatter_test.rs +3 -20
- compiler/core/tests/core/layout_test.rs +0 -20
- compiler/core/tests/core/loader_test.rs +1 -1
- compiler/core/tests/core/lower_test.rs +0 -17
- compiler/core/tests/core/main.rs +0 -3
- compiler/core/tests/core/override_parse_test.rs +0 -42
- compiler/core/tests/core/parser_test.rs +3 -15
- compiler/core/tests/core/purity_test.rs +0 -11
- compiler/core/tests/core/record_embed_test.rs +0 -230
- compiler/core/tests/core/record_init_parse_test.rs +17 -12
- compiler/core/tests/core/record_init_test.rs +5 -48
- compiler/core/tests/core/subtyping_test.rs +0 -43
- compiler/core/tests/core/uniqueness_test.rs +4 -16
- compiler/lsp/src/completion.rs +1 -23
- compiler/lsp/src/main.rs +8 -337
- compiler/tooling/tree-sitter-plum/grammar.js +5 -21
- compiler/tooling/tree-sitter-plum/queries/plum/format.scm +0 -2
- compiler/tooling/tree-sitter-plum/queries/plum/highlights.scm +0 -2
- compiler/tooling/tree-sitter-plum/src/grammar.json +0 -0
- compiler/tooling/tree-sitter-plum/src/node-types.json +0 -0
- compiler/tooling/tree-sitter-plum/src/parser.c +0 -0
- compiler/tooling/tree-sitter-plum/test/corpus/fip.txt +2 -2
- compiler/tooling/tree-sitter-plum/test/corpus/function.txt +6 -27
- compiler/tooling/tree-sitter-plum/test/corpus/struct_unified.txt +3 -10
- compiler/tooling/tree-sitter-plum/test/corpus/trait.txt +6 -26
- compiler/tooling/vscode-plum/syntaxes/plum.tmLanguage.json +1 -1
- compiler/wasm-codegen/src/calls.rs +0 -80
- compiler/wasm-codegen/src/context.rs +0 -3
- compiler/wasm-codegen/src/driver.rs +2 -12
- compiler/wasm-codegen/src/emit.rs +0 -2
- compiler/wasm-codegen/src/fnbody.rs +1 -10
- compiler/wasm-codegen/src/gctypes.rs +5 -78
- compiler/wasm-codegen/src/inference.rs +0 -7
- compiler/wasm-codegen/src/keys.rs +0 -6
- compiler/wasm-codegen/src/lib.rs +1 -1
- compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs +0 -15
- compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs +2 -11
- compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs +0 -1
- compiler/wasm-codegen/tests/wasm_codegen/e2e_types/subtyping.rs +0 -169
- compiler/wasm-codegen/tests/wasm_codegen/fusion_alloc_test.rs +3 -432
- docs/language.md +0 -32
- examples/structs_records.plum +10 -18
- examples/subtyping.plum +0 -97
- examples/unions.plum +10 -13
- tests/fbip/aliasing.plum +8 -24
- tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum +0 -22
- tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum +0 -21
- tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum +0 -29
- tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum +2 -6
- tests/wasm_codegen/e2e_records/{inherited_fields_methods_and_init_order.plum → init_fields_and_order.plum} +4 -10
- tests/wasm_codegen/e2e_records/override_calls_original.plum +0 -24
- tests/wasm_codegen/e2e_records/ship_example_values.plum +0 -26
- tests/wasm_codegen/e2e_records/through_a_base_value.plum +0 -24
compiler/checker/src/callcheck.rs
CHANGED
|
@@ -294,13 +294,7 @@ pub(crate) fn inferMethodCall(
|
|
|
294
294
|
}
|
|
295
295
|
return Ok(PlumType::TVar("_".to_string()));
|
|
296
296
|
};
|
|
297
|
-
// A base struct (or trait) value calls through its subtypes: the method's signature is the
|
|
298
|
-
// first subtype's that defines it.
|
|
299
|
-
let Some(sig) = ctx
|
|
300
|
-
.methods
|
|
301
|
-
|
|
297
|
+
let Some(sig) = ctx.methods.get(&(class_name.to_string(), name.to_string())) else {
|
|
302
|
-
.or_else(|| subtyping::firstSubtype(&class_name, |sub| ctx.methods.get(&(sub.to_string(), name.to_string()))))
|
|
303
|
-
else {
|
|
304
298
|
// `rec.add(a, b)` where `add` is an fn-typed FIELD (a "record of
|
|
305
299
|
// functions"): calls the stored closure. A method of the same name wins
|
|
306
300
|
// (it is found just above), so this is only reached when there is none.
|
compiler/checker/src/fncheck.rs
CHANGED
|
@@ -139,30 +139,10 @@ pub(crate) fn checkFnBody(f: &ast::Fn, global_env: &TypeEnv, ctx: &CheckCtx) ->
|
|
|
139
139
|
return errors;
|
|
140
140
|
}
|
|
141
141
|
(false, ast::FnBody::Extern) => {
|
|
142
|
-
// A bodiless instance method of a struct is abstract only when written
|
|
143
|
-
// `abstract fun` (its overrides are checked by the embed lowering).
|
|
144
|
-
if f.is_abstract {
|
|
145
|
-
if !f.has_self || f.type_param.is_none() {
|
|
146
|
-
errors.push(CheckError::at(
|
|
147
|
-
f.line,
|
|
148
|
-
f.column,
|
|
149
|
-
format!("fn '{}': abstract is only allowed on a struct instance method", f.name),
|
|
150
|
-
));
|
|
151
|
-
}
|
|
152
|
-
return errors;
|
|
153
|
-
}
|
|
154
142
|
errors.push(CheckError::resolution(
|
|
155
143
|
f.line,
|
|
156
144
|
f.column,
|
|
157
|
-
format!("fn '{}': missing a body (mark it
|
|
145
|
+
format!("fn '{}': missing a body (mark it extern)", f.name),
|
|
158
|
-
));
|
|
159
|
-
return errors;
|
|
160
|
-
}
|
|
161
|
-
(false, _) if f.is_abstract => {
|
|
162
|
-
errors.push(CheckError::at(
|
|
163
|
-
f.line,
|
|
164
|
-
f.column,
|
|
165
|
-
format!("fn '{}': abstract fn must not have a body", f.name),
|
|
166
146
|
));
|
|
167
147
|
return errors;
|
|
168
148
|
}
|
compiler/checker/src/infer.rs
CHANGED
|
@@ -447,11 +447,6 @@ pub(crate) fn inferExprInner(
|
|
|
447
447
|
let tenv = bindCondEnv(&t.condition, env, ctx)?;
|
|
448
448
|
let tt = inferExprWith(&t.then, &tenv, ctx, expected)?;
|
|
449
449
|
let et = inferExprWith(&t.else_, env, ctx, expected)?;
|
|
450
|
-
if tt != et {
|
|
451
|
-
if let Some(anc) = subtyping::commonAncestor(&tt, &et) {
|
|
452
|
-
return Ok(anc);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
450
|
unify(&tt, &et, ctx).map_err(|e| format!("inline if branches: {}", e))?;
|
|
456
451
|
// Each branch pins what the other leaves open (`if c then Ok(4) else
|
|
457
452
|
// Err(5)`: `Result[Int, _t1]` / `Result[_t2, Int]`); binding them both
|
compiler/checker/src/lib.rs
CHANGED
|
@@ -21,7 +21,6 @@ mod stmt;
|
|
|
21
21
|
mod tables;
|
|
22
22
|
mod typeconv;
|
|
23
23
|
mod unification;
|
|
24
|
-
pub mod subtyping;
|
|
25
24
|
pub mod types;
|
|
26
25
|
pub use types::{CheckErrorExt, DiagKind};
|
|
27
26
|
|
compiler/checker/src/matchcheck.rs
CHANGED
|
@@ -214,37 +214,6 @@ pub(crate) fn checkMatch(
|
|
|
214
214
|
return errors; // a subject failed to type — cases can't be checked meaningfully
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
// Exhaustiveness: enum / Bool / error-name subjects must cover every
|
|
218
|
-
// constructor (recursively through variant payloads) unless a catch-all
|
|
219
|
-
// arm exists. Literal (Int/Str/Float) subjects are left alone.
|
|
220
|
-
if let [PlumType::TNamed(tn)] = subject_types.as_slice() {
|
|
221
|
-
// A base struct: a subtype pattern needs a `_` arm (the other subtypes are not covered).
|
|
222
|
-
if subtyping::hasSubtypes(tn) {
|
|
223
|
-
let hasWildcard = m.cases.iter().any(|c| {
|
|
224
|
-
c.guard.is_none()
|
|
225
|
-
&& c.patterns.len() == 1
|
|
226
|
-
&& matches!(c.patterns[0], ast::CasePattern::Wildcard)
|
|
227
|
-
});
|
|
228
|
-
let namesSubtype = m.cases.iter().any(|c| {
|
|
229
|
-
c.patterns.iter().any(|p| match p {
|
|
230
|
-
ast::CasePattern::Class { name, .. } | ast::CasePattern::Name(name) => {
|
|
231
|
-
name != tn && subtyping::isSubtype(name, tn)
|
|
232
|
-
}
|
|
233
|
-
_ => false,
|
|
234
|
-
})
|
|
235
|
-
});
|
|
236
|
-
if !hasWildcard && namesSubtype {
|
|
237
|
-
errors.push(CheckError::at(
|
|
238
|
-
fn_pos.0,
|
|
239
|
-
fn_pos.1,
|
|
240
|
-
format!(
|
|
241
|
-
"fn '{}': match on {} needs a `_` arm: other subtypes of {} are not covered",
|
|
242
|
-
fn_name, tn, tn
|
|
243
|
-
),
|
|
244
|
-
));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
217
|
if let [subject_ty] = subject_types.as_slice() {
|
|
249
218
|
let rows: Vec<Vec<&ast::CasePattern>> = m
|
|
250
219
|
.cases
|
|
@@ -252,23 +221,19 @@ pub(crate) fn checkMatch(
|
|
|
252
221
|
.filter(|c| c.guard.is_none() && c.patterns.len() == 1)
|
|
253
222
|
.map(|c| vec![&c.patterns[0]])
|
|
254
223
|
.collect();
|
|
255
|
-
let isBaseSubject =
|
|
256
|
-
matches!(subject_ty, PlumType::TNamed(tn) if subtyping::hasSubtypes(tn));
|
|
257
224
|
if let Err(CoverFail::Reported(label, missing)) =
|
|
258
225
|
coverRows(rows, std::slice::from_ref(subject_ty), ctx)
|
|
259
226
|
{
|
|
260
|
-
if !isBaseSubject {
|
|
261
|
-
|
|
227
|
+
errors.push(CheckError::at(
|
|
262
|
-
|
|
228
|
+
fn_pos.0,
|
|
263
|
-
|
|
229
|
+
fn_pos.1,
|
|
264
|
-
|
|
230
|
+
format!(
|
|
265
|
-
|
|
231
|
+
"fn '{}': non-exhaustive match on {}: missing {}",
|
|
266
|
-
|
|
232
|
+
fn_name,
|
|
267
|
-
|
|
233
|
+
label,
|
|
268
|
-
|
|
234
|
+
missing.join(", ")
|
|
269
|
-
|
|
235
|
+
),
|
|
270
|
-
|
|
236
|
+
));
|
|
271
|
-
}
|
|
272
237
|
}
|
|
273
238
|
}
|
|
274
239
|
|
compiler/checker/src/patterns.rs
CHANGED
|
@@ -77,15 +77,6 @@ pub(crate) fn checkPattern(
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
ast::CasePattern::Class { name, fields, .. } => match ctx.enum_variants.get(name) {
|
|
80
|
-
Some(_)
|
|
81
|
-
if nominalName(subject_ty)
|
|
82
|
-
.and_then(|sn| subtyping::patternError(name, sn, ctx))
|
|
83
|
-
.is_some() =>
|
|
84
|
-
{
|
|
85
|
-
Err(nominalName(subject_ty)
|
|
86
|
-
.and_then(|sn| subtyping::patternError(name, sn, ctx))
|
|
87
|
-
.unwrap_or_default())
|
|
88
|
-
}
|
|
89
80
|
// Also how a record-shaped, single-variant enum (the `type X = ...`
|
|
90
81
|
// replacement) destructures — its one variant already IS the whole
|
|
91
82
|
// value, so there's no tag to check, just its declared fields (in
|
compiler/checker/src/state.rs
CHANGED
|
@@ -50,10 +50,6 @@ pub(crate) struct CheckState {
|
|
|
50
50
|
/// Operator expressions (by node id) on non-primitive operands -> the synthetic method call
|
|
51
51
|
/// (`a + b` -> `a.add(b)`) they resolve to.
|
|
52
52
|
pub(crate) op_calls: RefCell<HashMap<plum_core::ast::NodeId, std::rc::Rc<ast::FnCall>>>,
|
|
53
|
-
/// Nesting depth inside type arguments / fn types: conversion applies only at depth 0.
|
|
54
|
-
pub(crate) depth: Cell<usize>,
|
|
55
|
-
/// Struct name -> its strict ancestors, nearest first (filled by `subtyping::register`).
|
|
56
|
-
pub(crate) ancestors: RefCell<BTreeMap<String, Vec<String>>>,
|
|
57
53
|
/// Set around the top-level pattern of a `Pat := v` test: the next `checkPattern`
|
|
58
54
|
/// call (that top pattern) takes it, so only that one may be a bare payload variant.
|
|
59
55
|
pub(crate) allow_bare_top: Cell<bool>,
|
|
@@ -124,8 +120,8 @@ mod tests {
|
|
|
124
120
|
// a borrow held across a call that itself reaches the state (e.g. through
|
|
125
121
|
// inference) must not double-borrow the whole state
|
|
126
122
|
let n = withCheck(|outer| {
|
|
127
|
-
outer.
|
|
123
|
+
outer.fresh.set(1);
|
|
128
|
-
withCheck(|inner| inner.
|
|
124
|
+
withCheck(|inner| inner.fresh.get() + 1)
|
|
129
125
|
});
|
|
130
126
|
assert_eq!(n, 2);
|
|
131
127
|
resetCheck();
|
compiler/checker/src/subtyping.rs
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
//! Struct subtyping in the checker. A struct whose first embed names another
|
|
2
|
-
//! non-generic type is its subtype (`StructInfo::base`, recorded by the parser).
|
|
3
|
-
//! `register` keeps each struct's strict ancestors (nearest first) in the
|
|
4
|
-
//! check state so `unify` needs no `Source`.
|
|
5
|
-
|
|
6
|
-
use crate::state::withCheck;
|
|
7
|
-
use crate::types::PlumType;
|
|
8
|
-
use crate::CheckCtx;
|
|
9
|
-
use plum_core::ast;
|
|
10
|
-
use std::collections::BTreeMap;
|
|
11
|
-
|
|
12
|
-
/// Records every struct's strict ancestors, nearest first.
|
|
13
|
-
pub fn register(source: &ast::Source) {
|
|
14
|
-
let enums: BTreeMap<&str, &ast::Enum> = source
|
|
15
|
-
.items
|
|
16
|
-
.iter()
|
|
17
|
-
.filter_map(|i| match i {
|
|
18
|
-
ast::Item::Enum(e) => Some((e.name.as_str(), e)),
|
|
19
|
-
_ => None,
|
|
20
|
-
})
|
|
21
|
-
.collect();
|
|
22
|
-
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
|
23
|
-
for e in enums.values().filter(|e| e.isStruct()) {
|
|
24
|
-
let mut chain: Vec<String> = Vec::new();
|
|
25
|
-
let mut cur: &ast::Enum = e;
|
|
26
|
-
while let Some(b) = cur.base() {
|
|
27
|
-
let Some(be) = enums.get(b).copied() else { break };
|
|
28
|
-
if !be.generics.is_empty() || b == e.name || chain.iter().any(|c| c == b) {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
chain.push(b.to_string());
|
|
32
|
-
cur = be;
|
|
33
|
-
}
|
|
34
|
-
if !chain.is_empty() {
|
|
35
|
-
out.insert(e.name.clone(), chain);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
withCheck(|s| *s.ancestors.borrow_mut() = out);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/// Is `sub` the same struct as `base`, or one of its subtypes?
|
|
42
|
-
pub fn isSubtype(sub: &str, base: &str) -> bool {
|
|
43
|
-
sub == base
|
|
44
|
-
|| withCheck(|s| s.ancestors.borrow().get(sub).is_some_and(|v| v.iter().any(|n| n == base)))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Does `name` have at least one subtype (is it used as a base)?
|
|
48
|
-
pub fn hasSubtypes(name: &str) -> bool {
|
|
49
|
-
withCheck(|s| s.ancestors.borrow().values().any(|v| v.iter().any(|n| n == name)))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/// The first subtype of `base` (name order) for which `f` finds something.
|
|
53
|
-
pub fn firstSubtype<'a, R>(base: &str, f: impl Fn(&str) -> Option<R> + 'a) -> Option<R> {
|
|
54
|
-
let subs: Vec<String> = withCheck(|s| {
|
|
55
|
-
s.ancestors
|
|
56
|
-
.borrow()
|
|
57
|
-
.iter()
|
|
58
|
-
.filter(|(_, v)| v.iter().any(|n| n == base))
|
|
59
|
-
.map(|(k, _)| k.clone())
|
|
60
|
-
.collect()
|
|
61
|
-
});
|
|
62
|
-
subs.iter().find_map(|n| f(n))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/// May a value of type `actual` stand where `expected` is wanted? Top level
|
|
66
|
-
/// only: the caller (`unify`) skips this inside type arguments.
|
|
67
|
-
pub fn accepts(expected: &PlumType, actual: &PlumType) -> bool {
|
|
68
|
-
match (expected, actual) {
|
|
69
|
-
(PlumType::TNamed(e), PlumType::TNamed(a)) => e != a && isSubtype(a, e),
|
|
70
|
-
_ => false,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/// The nearest struct both types are subtypes of (or equal to), as a type.
|
|
75
|
-
pub fn commonAncestor(a: &PlumType, b: &PlumType) -> Option<PlumType> {
|
|
76
|
-
let (PlumType::TNamed(x), PlumType::TNamed(y)) = (a, b) else { return None };
|
|
77
|
-
if isSubtype(x, y) {
|
|
78
|
-
return Some(b.clone());
|
|
79
|
-
}
|
|
80
|
-
if isSubtype(y, x) {
|
|
81
|
-
return Some(a.clone());
|
|
82
|
-
}
|
|
83
|
-
let anc = withCheck(|s| s.ancestors.borrow().get(x).cloned().unwrap_or_default());
|
|
84
|
-
anc.into_iter().find(|n| isSubtype(y, n)).map(PlumType::TNamed)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/// Error for a pattern `pat` on a subject of struct type `subject` when `pat`
|
|
88
|
-
/// is a struct that is not a subtype of it.
|
|
89
|
-
pub fn patternError(pat: &str, subject: &str, ctx: &CheckCtx) -> Option<String> {
|
|
90
|
-
let isStructPat = ctx.enum_variants.get(pat).is_some_and(|i| i.enum_name == pat);
|
|
91
|
-
if isStructPat && pat != subject && hasSubtypes(subject) && !isSubtype(pat, subject) {
|
|
92
|
-
return Some(format!("pattern {pat} is not a subtype of {subject}"));
|
|
93
|
-
}
|
|
94
|
-
None
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/// Runs `f` (a unify of a nested type position), where no subtype conversion applies.
|
|
98
|
-
pub fn nested<R>(f: impl FnOnce() -> R) -> R {
|
|
99
|
-
withCheck(|s| s.depth.set(s.depth.get() + 1));
|
|
100
|
-
let r = f();
|
|
101
|
-
withCheck(|s| s.depth.set(s.depth.get() - 1));
|
|
102
|
-
r
|
|
103
|
-
}
|
compiler/checker/src/tables.rs
CHANGED
|
@@ -110,7 +110,6 @@ pub struct CheckCtx<'a> {
|
|
|
110
110
|
/// `plum-wasm-codegen`, which needs the same tables to resolve `self`, field access,
|
|
111
111
|
/// and method dispatch during code generation.
|
|
112
112
|
pub fn buildGlobalTables(source: &ast::Source) -> (TypeEnv, MethodEnv, EnumVariants) {
|
|
113
|
-
subtyping::register(source);
|
|
114
113
|
let conv = plumTypeFromAstG;
|
|
115
114
|
let mut global_env: TypeEnv = TypeEnv::new();
|
|
116
115
|
let mut methods: MethodEnv = BTreeMap::new();
|
compiler/checker/src/unification.rs
CHANGED
|
@@ -147,7 +147,7 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
|
|
|
147
147
|
// Structural generics: same base, arguments unify pairwise.
|
|
148
148
|
(PlumType::TApp(a, xs), PlumType::TApp(b, ys)) if a == b && xs.len() == ys.len() => {
|
|
149
149
|
for (x, y) in xs.iter().zip(ys.iter()) {
|
|
150
|
-
|
|
150
|
+
unify(x, y, ctx)
|
|
151
151
|
.map_err(|e| format!("type mismatch: expected {}, found {} ({})", t1, t2, e))?;
|
|
152
152
|
}
|
|
153
153
|
Ok(())
|
|
@@ -192,12 +192,10 @@ pub fn unify(t1: &PlumType, t2: &PlumType, ctx: &CheckCtx) -> Result<(), String>
|
|
|
192
192
|
}
|
|
193
193
|
(PlumType::TFun(ps1, r1), PlumType::TFun(ps2, r2)) if ps1.len() == ps2.len() => {
|
|
194
194
|
for (p1, p2) in ps1.iter().zip(ps2.iter()) {
|
|
195
|
-
|
|
195
|
+
unify(p1, p2, ctx)?;
|
|
196
196
|
}
|
|
197
|
-
|
|
197
|
+
unify(r1, r2, ctx)
|
|
198
198
|
}
|
|
199
|
-
// A subtype where its base struct is expected (top level only).
|
|
200
|
-
_ if withCheck(|s| s.depth.get()) == 0 && subtyping::accepts(t1, t2) => Ok(()),
|
|
201
199
|
_ => Err(format!("type mismatch: expected {}, found {}", t1, t2)),
|
|
202
200
|
}
|
|
203
201
|
}
|
|
@@ -238,7 +236,6 @@ pub(crate) fn unifyArg(
|
|
|
238
236
|
unify(expected, actual, ctx).map_err(|e| format!("{}{}", e, wrapHint(expected, actual, ctx)))?;
|
|
239
237
|
if let (PlumType::TNamed(variant_name), PlumType::TNamed(actual_name)) = (expected, actual) {
|
|
240
238
|
if variant_name != actual_name
|
|
241
|
-
&& !subtyping::accepts(expected, actual)
|
|
242
239
|
&& ctx.enum_variants.contains_key(variant_name)
|
|
243
240
|
&& !exprIsVariant(actual_expr, variant_name)
|
|
244
241
|
{
|
compiler/checker/tests/checker/features/mutability.rs
CHANGED
|
@@ -23,14 +23,6 @@ fn mut_field_assign_ok() {
|
|
|
23
23
|
run("struct Node(mut x: Int)\nfun f(n: Node) -> Int =\n n.x = 3\n n.x\n").unwrap();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
#[test]
|
|
27
|
-
fn embedded_mut_field_ok() {
|
|
28
|
-
let defs = "struct Pos(mut x: Int, y: Int)\nstruct Hut(Pos(x, y), k: Int)\n";
|
|
29
|
-
run(&format!("{defs}fun f(h: Hut) -> Int =\n h.x = 3\n h.x\n")).unwrap();
|
|
30
|
-
let e = run(&format!("{defs}fun f(h: Hut) -> Int =\n h.y = 3\n h.y\n")).unwrap_err();
|
|
31
|
-
assert!(e.contains("Hut.y is immutable"), "{e}");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
#[test]
|
|
35
27
|
fn struct_local_assign_ok() {
|
|
36
28
|
run("struct Vec2(x: Int)\nfun f() -> Int =\n v := Vec2(x: 1)\n v.x = 3\n v.x\n").unwrap();
|
compiler/checker/tests/checker/main.rs
CHANGED
|
@@ -10,4 +10,3 @@ mod fip_test;
|
|
|
10
10
|
mod fusion_pipeline_test;
|
|
11
11
|
mod inline_variants_tests;
|
|
12
12
|
mod question_tests;
|
|
13
|
-
mod subtyping_tests;
|
compiler/checker/tests/checker/question_tests.rs
CHANGED
|
@@ -12,7 +12,6 @@ enum Result[T, E] =
|
|
|
12
12
|
| Err(error: E)
|
|
13
13
|
|
|
14
14
|
struct Actor(x: Int)
|
|
15
|
-
struct Ship(Actor(x), s: Int)
|
|
16
15
|
enum ErrA =
|
|
17
16
|
| NoSuchPath
|
|
18
17
|
| Other
|
|
@@ -72,11 +71,6 @@ fn question_in_closure_errors() {
|
|
|
72
71
|
);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
#[test]
|
|
76
|
-
fn subtype_into_option() {
|
|
77
|
-
ok("fun f(s: Ship) -> Option[Actor] = Some(s)");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
74
|
#[test]
|
|
81
75
|
fn unit_value() {
|
|
82
76
|
ok("fun f() -> Result[Unit, ErrA] = Ok(Unit)");
|
compiler/checker/tests/checker/subtyping_tests.rs
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
#![allow(non_snake_case)]
|
|
2
|
-
//! Struct subtyping in the checker: a subtype is accepted where its base is expected
|
|
3
|
-
//! (top level only), `if` branches join to the nearest common ancestor, and a
|
|
4
|
-
//! subtype pattern on a base subject needs a `_`.
|
|
5
|
-
use plum_checker::checkSource;
|
|
6
|
-
|
|
7
|
-
const DEFS: &str = "\
|
|
8
|
-
struct Actor(x: Int) =
|
|
9
|
-
fun draw(self) -> Int = 1
|
|
10
|
-
|
|
11
|
-
struct Ship(Actor(x), s: Int) =
|
|
12
|
-
override fun draw(self) -> Int = 2
|
|
13
|
-
|
|
14
|
-
struct Boat(Actor(x), w: Int) =
|
|
15
|
-
override fun draw(self) -> Int = 3
|
|
16
|
-
|
|
17
|
-
struct Plain(q: Int)
|
|
18
|
-
";
|
|
19
|
-
|
|
20
|
-
fn run(body: &str) -> Result<(), String> {
|
|
21
|
-
let src = format!("{DEFS}\n{body}\n");
|
|
22
|
-
let mut source = plum_test_support::parseRawSource(&src);
|
|
23
|
-
plum_core::lower::frontEndDiag(&mut source).map_err(|e| format!("lower: {e:?}"))?;
|
|
24
|
-
checkSource(&source).map_err(|e| e.into_iter().map(|x| x.message).collect::<Vec<_>>().join(" | "))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#[test]
|
|
28
|
-
fn subtypeArgumentAccepted() {
|
|
29
|
-
run("fun f(a: Actor) -> Int = a.draw()\nfun main() -> Int = f(Ship(x: 1, s: 2))").unwrap();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#[test]
|
|
33
|
-
fn baseArgumentToSubtypeParamRejected() {
|
|
34
|
-
assert!(run("fun f(a: Ship) -> Int = a.s\nfun main() -> Int = f(Actor(x: 1))").is_err());
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#[test]
|
|
38
|
-
fn unrelatedStructRejected() {
|
|
39
|
-
assert!(run("fun f(a: Actor) -> Int = a.x\nfun main() -> Int = f(Plain(q: 1))").is_err());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
#[test]
|
|
43
|
-
fn sliceElementAccepted() {
|
|
44
|
-
run("fun main() -> Int =\n xs := Slice[Actor]()\n xs.push(Ship(x: 1, s: 2))\n 0").unwrap();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
#[test]
|
|
48
|
-
fn nestedPositionRejected() {
|
|
49
|
-
let r = run("fun f(a: Slice[Actor]) -> Int = 0\nfun g(xs: Slice[Ship]) -> Int = f(xs)");
|
|
50
|
-
assert!(r.is_err(), "Slice[Ship] must not be a Slice[Actor]");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#[test]
|
|
54
|
-
fn ifBranchesJoinToAncestor() {
|
|
55
|
-
run("fun f(c: Bool) -> Actor = if c then Ship(x: 1, s: 2) else Boat(x: 1, w: 3)").unwrap();
|
|
56
|
-
run("fun main(c: Bool) -> Int =\n a := if c then Ship(x: 1, s: 2) else Boat(x: 1, w: 3)\n a.draw()").unwrap();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#[test]
|
|
60
|
-
fn subtypePatternWithWildcard() {
|
|
61
|
-
run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s\n _ => 0").unwrap();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#[test]
|
|
65
|
-
fn subtypePatternWithoutWildcardErrors() {
|
|
66
|
-
assert!(run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s").is_err());
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#[test]
|
|
70
|
-
fn unrelatedPatternErrors() {
|
|
71
|
-
assert!(run("fun f(a: Actor) -> Int = match a\n Plain(q) => q\n _ => 0").is_err());
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
#[test]
|
|
75
|
-
fn matchArmsAgainstBaseReturn() {
|
|
76
|
-
run("fun f(c: Bool) -> Actor = match c\n true => Ship(x: 1, s: 2)\n false => Boat(x: 1, w: 3)").unwrap();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#[test]
|
|
80
|
-
fn noWildcardMessageIsClear() {
|
|
81
|
-
let e = run("fun f(a: Actor) -> Int = match a\n Ship(x, s) => s").unwrap_err();
|
|
82
|
-
assert!(e.contains("match on Actor needs a `_` arm"), "{e}");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
#[test]
|
|
86
|
-
fn abstractStructMethodAccepted() {
|
|
87
|
-
let src = "struct Actor(x: Int) =\n abstract fun draw(self) -> Int\n\nstruct Ship(Actor(x)) =\n override fun draw(self) -> Int = 2\n\nfun f(a: Actor) -> Int = a.draw()\n";
|
|
88
|
-
let mut source = plum_test_support::parseRawSource(src);
|
|
89
|
-
plum_core::lower::frontEndDiag(&mut source).unwrap();
|
|
90
|
-
checkSource(&source).unwrap();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#[test]
|
|
94
|
-
fn bodilessTopLevelFnStillErrors() {
|
|
95
|
-
let src = "fun g() -> Int\n";
|
|
96
|
-
let mut source = plum_test_support::parseRawSource(src);
|
|
97
|
-
plum_core::lower::frontEndDiag(&mut source).unwrap();
|
|
98
|
-
let e = checkSource(&source).unwrap_err();
|
|
99
|
-
assert!(e.iter().any(|x| x.message.contains("missing a body")));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
fn checkErrs(src: &str) -> Vec<String> {
|
|
103
|
-
let mut source = plum_test_support::parseRawSource(src);
|
|
104
|
-
plum_core::lower::frontEndDiag(&mut source).unwrap();
|
|
105
|
-
checkSource(&source).unwrap_err().into_iter().map(|x| x.message).collect()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#[test]
|
|
109
|
-
fn bodilessMethodWithoutAbstractErrors() {
|
|
110
|
-
let e = checkErrs("struct Actor(x: Int) =\n fun draw(self) -> Int\n");
|
|
111
|
-
assert!(e.iter().any(|m| m.contains("fn 'draw': missing a body (mark it abstract or extern)")), "{e:?}");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
#[test]
|
|
115
|
-
fn abstractWithBodyErrors() {
|
|
116
|
-
let e = checkErrs("struct Actor(x: Int) =\n abstract fun draw(self) -> Int = 1\n");
|
|
117
|
-
assert!(e.iter().any(|m| m.contains("abstract fn must not have a body")), "{e:?}");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#[test]
|
|
121
|
-
fn abstractTopLevelErrors() {
|
|
122
|
-
let e = checkErrs("abstract fun g() -> Int\n");
|
|
123
|
-
assert!(e.iter().any(|m| m.contains("abstract is only allowed on a struct instance method")), "{e:?}");
|
|
124
|
-
}
|
compiler/checker/tests/fixtures/custom_actor_probe2.plum
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import std/Int
|
|
2
|
-
import std/Float
|
|
3
|
-
import std/Str
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Node(id: Int, mut x: Float, y: Float, kind: Kind)
|
|
7
|
-
|
|
8
|
-
enum Kind =
|
|
9
|
-
| Group(children: Slice[Node])
|
|
10
|
-
| Label(text: Str, size: Float)
|
|
11
|
-
| Custom(actor: Actor)
|
|
12
|
-
|
|
13
|
-
# Behaviour only: the game's structs embed it as their base.
|
|
14
|
-
struct Actor() =
|
|
15
|
-
abstract fun act(self, n: Node, dt: Float) -> Node
|
|
16
|
-
abstract fun score(self, n: Node) -> Float
|
|
17
|
-
|
|
18
|
-
struct Ship(Actor, mut clock: Float) =
|
|
19
|
-
override fun act(self, n: Node, dt: Float) -> Node =
|
|
20
|
-
self.clock = self.clock + dt
|
|
21
|
-
n
|
|
22
|
-
override fun score(self, n: Node) -> Float = self.clock
|
|
23
|
-
|
|
24
|
-
struct Orb(Actor, vx: Float) =
|
|
25
|
-
override fun act(self, n: Node, dt: Float) -> Node =
|
|
26
|
-
n.x = n.x + self.vx * dt
|
|
27
|
-
n
|
|
28
|
-
override fun score(self, n: Node) -> Float = n.x
|
|
29
|
-
|
|
30
|
-
fun actorOf(n: Node) -> Actor? = match n.kind
|
|
31
|
-
Custom(a) => a
|
|
32
|
-
_ => null
|
|
33
|
-
|
|
34
|
-
fun update(n: Node, dt: Float) -> Node =
|
|
35
|
-
if a := actorOf(n)
|
|
36
|
-
return a.act(n, dt)
|
|
37
|
-
match n.kind
|
|
38
|
-
Group(ks) =>
|
|
39
|
-
_ := ks.mapInPlace(|k| update(k, dt))
|
|
40
|
-
_ => 0
|
|
41
|
-
n
|
|
42
|
-
|
|
43
|
-
fun total(n: Node) -> Float = match n.kind
|
|
44
|
-
Group(ks) =>
|
|
45
|
-
s := 0.0
|
|
46
|
-
for i := range ks.length()
|
|
47
|
-
s = s + total(ks.at(i))
|
|
48
|
-
s
|
|
49
|
-
Custom(a) => a.score(n)
|
|
50
|
-
Label(_, _) => 0.0
|
|
51
|
-
|
|
52
|
-
test "custom_actor"
|
|
53
|
-
ks := Slice[Node]()
|
|
54
|
-
ks.push(Node(id: 1, x: 1.0, y: 0.0, kind: Custom(actor: Orb(vx: 2.0))))
|
|
55
|
-
ks.push(Node(id: 2, x: 0.0, y: 0.0, kind: Custom(actor: Ship(clock: 0.0))))
|
|
56
|
-
root := Node(id: 0, x: 0.0, y: 0.0, kind: Group(children: ks))
|
|
57
|
-
r := update(root, 0.5)
|
|
58
|
-
assert total(r) == 2.0 + 0.5
|
compiler/core/src/ast.rs
CHANGED
|
@@ -443,20 +443,9 @@ pub enum TypeKind {
|
|
|
443
443
|
|
|
444
444
|
#[derive(Debug, Clone, PartialEq, Default)]
|
|
445
445
|
pub struct StructInfo {
|
|
446
|
-
/// Always `Boxed` — generated from an inline variant
|
|
446
|
+
/// Always `Boxed` — generated from an inline variant or a `fun init(self)`
|
|
447
|
-
/// later clears `embeds`, so the parser records it here) or a
|
|
448
|
-
///
|
|
447
|
+
/// method. Otherwise the layout is decided only by `layout::structLayout`.
|
|
449
|
-
/// `layout::structLayout`.
|
|
450
448
|
pub forceBoxed: bool,
|
|
451
|
-
/// Structs listed first in a `struct` field list (`struct H(E, k: Int)`).
|
|
452
|
-
pub embeds: Vec<String>,
|
|
453
|
-
/// The same structs with the fields each exposes in the header
|
|
454
|
-
/// (`Actor(x, y)` -> ("Actor", ["x","y"]); bare `Actor` -> empty list).
|
|
455
|
-
pub embedFields: Vec<(String, Vec<String>)>,
|
|
456
|
-
/// The FIRST embedded name of a struct: the struct it subtypes when that
|
|
457
|
-
/// is a (non-generic) struct item. The parser records it because lowering
|
|
458
|
-
/// clears `embeds`.
|
|
459
|
-
pub base: Option<String>,
|
|
460
449
|
}
|
|
461
450
|
|
|
462
451
|
impl Enum {
|
|
@@ -475,14 +464,6 @@ impl Enum {
|
|
|
475
464
|
TypeKind::Enum => None,
|
|
476
465
|
}
|
|
477
466
|
}
|
|
478
|
-
/// The embedded structs with their exposed fields; empty for an enum.
|
|
479
|
-
pub fn embedFields(&self) -> &[(String, Vec<String>)] {
|
|
480
|
-
self.structInfo().map_or(&[], |i| i.embedFields.as_slice())
|
|
481
|
-
}
|
|
482
|
-
/// The struct this one subtypes, if it is a struct that embeds one.
|
|
483
|
-
pub fn base(&self) -> Option<&str> {
|
|
484
|
-
self.structInfo()?.base.as_deref()
|
|
485
|
-
}
|
|
486
467
|
}
|
|
487
468
|
|
|
488
469
|
#[derive(Debug, Clone, PartialEq)]
|
|
@@ -538,11 +519,6 @@ pub struct Fn {
|
|
|
538
519
|
pub is_export: bool,
|
|
539
520
|
/// A `test` block lowered to a fn (`tests_lower`); its name is generated.
|
|
540
521
|
pub is_test: bool,
|
|
541
|
-
/// `override fun`: replaces a trait or embedded-record method (checked by the embed lowering).
|
|
542
|
-
pub is_override: bool,
|
|
543
|
-
/// `abstract fun m(self) -> T` (no body): an abstract struct method that every
|
|
544
|
-
/// subtype must override.
|
|
545
|
-
pub is_abstract: bool,
|
|
546
522
|
/// `fip fun`: every spread and `Array.with` in the body must be proved in place (driver check).
|
|
547
523
|
pub is_fip: bool,
|
|
548
524
|
/// The fn declared `self` as its first parameter (an instance method). A
|
compiler/core/src/closure_spec.rs
CHANGED
|
@@ -60,7 +60,6 @@ pub fn specialiseWith(src: &mut Source, hints: &Hints) -> Vec<String> {
|
|
|
60
60
|
ne,
|
|
61
61
|
fns: HashMap::new(),
|
|
62
62
|
methodsByName: HashMap::new(),
|
|
63
|
-
virt: crate::subtyping::virtualMethods(src),
|
|
64
63
|
specs: Vec::new(),
|
|
65
64
|
copies: HashMap::new(),
|
|
66
65
|
perCallee: HashMap::new(),
|
|
@@ -158,8 +157,6 @@ struct Ctx<'h> {
|
|
|
158
157
|
ne: HashMap<String, Vec<usize>>,
|
|
159
158
|
fns: HashMap<String, Fn>,
|
|
160
159
|
methodsByName: HashMap<String, Vec<String>>,
|
|
161
|
-
/// `(type, method)` pairs some subtype overrides: calls on them dispatch dynamically.
|
|
162
|
-
virt: HashSet<(String, String)>,
|
|
163
160
|
specs: Vec<Spec>,
|
|
164
161
|
/// (callee key, param index, spec) -> copy fn name; `None` when it cannot be made.
|
|
165
162
|
copies: HashMap<(String, usize, usize), Option<String>>,
|
|
@@ -245,16 +242,10 @@ impl<'h> Ctx<'h> {
|
|
|
245
242
|
};
|
|
246
243
|
match ty {
|
|
247
244
|
Some(t) => {
|
|
248
|
-
if self.virt.contains(&(t.clone(), c.name.clone())) {
|
|
249
|
-
return None;
|
|
250
|
-
}
|
|
251
245
|
let key = format!("{}.{}", t, c.name);
|
|
252
246
|
self.fns.contains_key(&key).then_some(key)
|
|
253
247
|
}
|
|
254
248
|
None => {
|
|
255
|
-
if self.virt.iter().any(|(_, m)| *m == c.name) {
|
|
256
|
-
return None;
|
|
257
|
-
}
|
|
258
249
|
let v = self.methodsByName.get(&c.name)?;
|
|
259
250
|
if v.len() == 1 {
|
|
260
251
|
Some(v[0].clone())
|
compiler/core/src/ctors.rs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
//! Constructor lowering for structs that
|
|
1
|
+
//! Constructor lowering for structs that have an `init`.
|
|
2
2
|
//!
|
|
3
|
-
//! A struct `
|
|
3
|
+
//! A struct with `fun init(self)` is constructed through a generated fn
|
|
4
|
-
//! `Ship__new(x,
|
|
4
|
+
//! `Ship__new(x, tex) -> Ship`: the top-level `self.f = expr` statements of the
|
|
5
|
-
//!
|
|
5
|
+
//! struct's `init` become locals, the struct is built by ONE constructor call once
|
|
6
|
-
//!
|
|
6
|
+
//! every field has a value, and the remaining init statements run afterwards as a
|
|
7
|
-
//!
|
|
7
|
+
//! generated method (`__init_<Owner>`) with `self` bound.
|
|
8
8
|
//! Every user `Ship(...)` call becomes `Ship__new(...)`. Idempotent: lowering
|
|
9
|
-
//!
|
|
9
|
+
//! removes the `init`s, so a second run finds nothing.
|
|
10
10
|
use std::collections::{HashMap, HashSet};
|
|
11
11
|
|
|
12
12
|
use crate::ast::*;
|
|
@@ -30,88 +30,6 @@ struct Plan {
|
|
|
30
30
|
params: Vec<(String, Type)>,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/// What an embedded struct contributes to its embedder's constructor: the fields it
|
|
34
|
-
/// declares itself (header plus `self.f: T = ..` in its `init`; never the ones it
|
|
35
|
-
/// embedded) and its `init`. Private to this pass.
|
|
36
|
-
struct EmbedInit {
|
|
37
|
-
#[allow(dead_code)]
|
|
38
|
-
name: String,
|
|
39
|
-
fields: Vec<EnumParam>,
|
|
40
|
-
init: Option<Fn>,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/// An `EmbedInit` for every struct that some `embedFields` entry names, computed from
|
|
44
|
-
/// the post-`lowerEmbeds` items (a struct's variant holds spliced fields, then its own).
|
|
45
|
-
fn embedInits(source: &Source) -> HashMap<String, EmbedInit> {
|
|
46
|
-
let structs: HashMap<&str, &Enum> = source
|
|
47
|
-
.items
|
|
48
|
-
.iter()
|
|
49
|
-
.filter_map(|i| match i {
|
|
50
|
-
Item::Enum(e) if e.isStruct() => Some((e.name.as_str(), e)),
|
|
51
|
-
_ => None,
|
|
52
|
-
})
|
|
53
|
-
.collect();
|
|
54
|
-
let mut out: HashMap<String, EmbedInit> = HashMap::new();
|
|
55
|
-
let wanted: Vec<String> = structs
|
|
56
|
-
.values()
|
|
57
|
-
.flat_map(|e| e.embedFields().iter().map(|(n, _)| n.clone()))
|
|
58
|
-
.filter(|n| structs.contains_key(n.as_str()))
|
|
59
|
-
.collect();
|
|
60
|
-
for n in wanted {
|
|
61
|
-
embedInitOf(source, &structs, &n, &mut out, &mut Vec::new());
|
|
62
|
-
}
|
|
63
|
-
out
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
fn embedInitOf(
|
|
67
|
-
source: &Source,
|
|
68
|
-
structs: &HashMap<&str, &Enum>,
|
|
69
|
-
name: &str,
|
|
70
|
-
out: &mut HashMap<String, EmbedInit>,
|
|
71
|
-
stack: &mut Vec<String>,
|
|
72
|
-
) {
|
|
73
|
-
if out.contains_key(name) || stack.iter().any(|s| s == name) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
let Some(e) = structs.get(name) else { return };
|
|
77
|
-
stack.push(name.to_string());
|
|
78
|
-
let mut taken: HashSet<String> = HashSet::new();
|
|
79
|
-
for (n, _) in e.embedFields() {
|
|
80
|
-
if structs.contains_key(n.as_str()) {
|
|
81
|
-
embedInitOf(source, structs, n, out, stack);
|
|
82
|
-
if let Some(c) = out.get(n) {
|
|
83
|
-
taken.extend(c.fields.iter().map(|p| p.name.clone()));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
let mut fields: Vec<EnumParam> = Vec::new();
|
|
88
|
-
if let Some(v) = e.variants.first() {
|
|
89
|
-
for (i, (n, t)) in v.field_names.iter().zip(v.fields.iter()).enumerate() {
|
|
90
|
-
if !taken.contains(n) {
|
|
91
|
-
fields.push(EnumParam {
|
|
92
|
-
name: n.clone(),
|
|
93
|
-
ty: t.clone(),
|
|
94
|
-
isMut: v.mut_fields.get(i).copied().unwrap_or(false),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
let init = initOf(&source.items, name).cloned();
|
|
100
|
-
if let Some(init) = &init {
|
|
101
|
-
for st in initStmts(init) {
|
|
102
|
-
let Some((fname, val, ann)) = fieldAssign(&st) else { continue };
|
|
103
|
-
if taken.contains(fname) || fields.iter().any(|p| p.name == fname) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if let Some(t) = ann.cloned().or_else(|| inferType(val)) {
|
|
107
|
-
fields.push(EnumParam { name: fname.to_string(), isMut: false, ty: t });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
stack.pop();
|
|
112
|
-
out.insert(name.to_string(), EmbedInit { name: name.to_string(), fields, init });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
33
|
fn initOf<'a>(items: &'a [Item], rec: &str) -> Option<&'a Fn> {
|
|
116
34
|
items.iter().find_map(|i| match i {
|
|
117
35
|
Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == "init" && f.has_self => {
|
|
@@ -197,19 +115,6 @@ fn mentionsSelf(s: &Stmt) -> bool {
|
|
|
197
115
|
|
|
198
116
|
pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
199
117
|
let mut errors: Vec<String> = Vec::new();
|
|
200
|
-
let embeded = embedInits(source);
|
|
201
|
-
|
|
202
|
-
// Embedded-struct inits (cloned).
|
|
203
|
-
let mut traitInits: HashMap<String, Fn> = HashMap::new();
|
|
204
|
-
|
|
205
|
-
for (n, ei) in &embeded {
|
|
206
|
-
if let Some(f) = &ei.init {
|
|
207
|
-
if !f.params.is_empty() || f.returns.is_some() {
|
|
208
|
-
errors.push("init takes only self".to_string());
|
|
209
|
-
}
|
|
210
|
-
traitInits.insert(n.clone(), f.clone());
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
118
|
|
|
214
119
|
// Step A: constructor records and their parameters.
|
|
215
120
|
let mut plans: HashMap<String, Plan> = HashMap::new();
|
|
@@ -220,47 +125,20 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
220
125
|
continue;
|
|
221
126
|
}
|
|
222
127
|
let init = initOf(&source.items, &e.name);
|
|
223
|
-
if e.embedFields().is_empty() && init.is_none() {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
128
|
+
let Some(f) = init else { continue };
|
|
227
|
-
|
|
129
|
+
if !f.params.is_empty() || f.returns.is_some() {
|
|
228
|
-
|
|
130
|
+
errors.push("init takes only self".to_string());
|
|
229
|
-
}
|
|
230
131
|
}
|
|
231
132
|
if !e.generics.is_empty() {
|
|
232
133
|
errors.push(format!(
|
|
233
|
-
"record {} is generic: init
|
|
134
|
+
"record {} is generic: init is not supported on generic records",
|
|
234
135
|
e.name
|
|
235
136
|
));
|
|
236
137
|
continue;
|
|
237
138
|
}
|
|
238
139
|
let v = &e.variants[0];
|
|
239
|
-
let mut traitFieldCount = 0;
|
|
240
|
-
let
|
|
140
|
+
let params: Vec<(String, Type)> =
|
|
241
|
-
let typeOf = |n: &str| -> Option<Type> {
|
|
242
|
-
v.field_names
|
|
243
|
-
.iter()
|
|
244
|
-
.position(|x| x == n)
|
|
245
|
-
.map(|i| v.fields[i].clone())
|
|
246
|
-
};
|
|
247
|
-
for (tname, exposed) in e.embedFields() {
|
|
248
|
-
let (kind, tfields) = match embeded.get(tname) {
|
|
249
|
-
Some(ei) => ("struct", &ei.fields),
|
|
250
|
-
None => continue,
|
|
251
|
-
};
|
|
252
|
-
traitFieldCount += tfields.len();
|
|
253
|
-
for f in exposed {
|
|
254
|
-
if !tfields.iter().any(|tf| tf.name == *f) {
|
|
255
|
-
errors.push(format!("{} {} has no field {}", kind, tname, f));
|
|
256
|
-
} else if let Some(ft) = typeOf(f) {
|
|
257
|
-
params.push((f.clone(), ft));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
for i in traitFieldCount.min(v.field_names.len())..v.field_names.len() {
|
|
262
|
-
|
|
141
|
+
v.field_names.iter().cloned().zip(v.fields.iter().cloned()).collect();
|
|
263
|
-
}
|
|
264
142
|
plans.insert(e.name.clone(), Plan { params });
|
|
265
143
|
order.push(ii);
|
|
266
144
|
}
|
|
@@ -281,22 +159,18 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
281
159
|
_ => {}
|
|
282
160
|
}
|
|
283
161
|
}
|
|
284
|
-
for m in traitInits.values_mut() {
|
|
285
|
-
rewriteFn(m, &plans, &mut errors);
|
|
286
|
-
}
|
|
287
162
|
}
|
|
288
163
|
|
|
289
164
|
// Step C: build each constructor.
|
|
290
165
|
let mut newItems: Vec<Item> = Vec::new();
|
|
291
166
|
let mut dropInits: HashSet<String> = HashSet::new();
|
|
292
167
|
for ii in order {
|
|
293
|
-
let (rname, line, column,
|
|
168
|
+
let (rname, line, column, names0, tys0, nparams, egens) = {
|
|
294
169
|
let Item::Enum(e) = &source.items[ii] else { continue };
|
|
295
170
|
(
|
|
296
171
|
e.name.clone(),
|
|
297
172
|
e.line,
|
|
298
173
|
e.column,
|
|
299
|
-
e.embedFields().to_vec(),
|
|
300
174
|
e.variants[0].field_names.clone(),
|
|
301
175
|
e.variants[0].fields.clone(),
|
|
302
176
|
plans[&e.name].params.clone(),
|
|
@@ -306,11 +180,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
306
180
|
let recInit = initOf(&source.items, &rname).cloned();
|
|
307
181
|
let file = recInit.as_ref().map(|f| f.file.clone()).unwrap_or_else(|| source.file.clone());
|
|
308
182
|
let mut inits: Vec<(String, Fn)> = Vec::new();
|
|
309
|
-
for (tname, _) in &embeds {
|
|
310
|
-
if let Some(f) = traitInits.get(tname) {
|
|
311
|
-
inits.push((tname.clone(), f.clone()));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
183
|
if let Some(f) = recInit {
|
|
315
184
|
inits.push((rname.clone(), f));
|
|
316
185
|
}
|
|
@@ -367,10 +236,7 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
367
236
|
let firstUnset = names.iter().find(|n| !set.contains(*n)).cloned().unwrap_or_default();
|
|
368
237
|
if let Some((fname, val, _)) = fieldAssign(&st) {
|
|
369
238
|
if nparams.iter().any(|(n, _)| n == fname) {
|
|
370
|
-
if *owner == rname {
|
|
371
|
-
|
|
239
|
+
errors.push(format!("{} init sets constructor parameter {}", rname, fname));
|
|
372
|
-
}
|
|
373
|
-
// An embedded init never overrides what the caller passed.
|
|
374
240
|
continue;
|
|
375
241
|
}
|
|
376
242
|
if !names.iter().any(|n| n == fname) {
|
|
@@ -482,8 +348,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
482
348
|
is_extern: false,
|
|
483
349
|
is_export: false,
|
|
484
350
|
is_test: false,
|
|
485
|
-
is_override: false,
|
|
486
|
-
is_abstract: false,
|
|
487
351
|
is_fip: false,
|
|
488
352
|
has_self: true,
|
|
489
353
|
params: vec![],
|
|
@@ -502,8 +366,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
502
366
|
is_extern: false,
|
|
503
367
|
is_export: false,
|
|
504
368
|
is_test: false,
|
|
505
|
-
is_override: false,
|
|
506
|
-
is_abstract: false,
|
|
507
369
|
is_fip: false,
|
|
508
370
|
has_self: false,
|
|
509
371
|
params: nparams
|
|
@@ -519,9 +381,6 @@ pub fn lowerCtors(source: &mut Source) -> Vec<String> {
|
|
|
519
381
|
}));
|
|
520
382
|
|
|
521
383
|
if let Item::Enum(e) = &mut source.items[ii] {
|
|
522
|
-
if let Some(info) = e.structInfoMut() {
|
|
523
|
-
info.embedFields.clear();
|
|
524
|
-
}
|
|
525
384
|
e.variants[0].field_names = names;
|
|
526
385
|
e.variants[0].fields = tys;
|
|
527
386
|
}
|
compiler/core/src/drive.rs
CHANGED
|
@@ -99,14 +99,12 @@ struct Driver {
|
|
|
99
99
|
scope: Scope,
|
|
100
100
|
/// record -> (its generics, (field name, field type)), for typing `x.field`
|
|
101
101
|
recFields: HashMap<String, (Vec<String>, Vec<(String, Type)>)>,
|
|
102
|
-
/// `Type.method` of every method with a receiver, externs
|
|
102
|
+
/// `Type.method` of every method with a receiver, externs included ->
|
|
103
103
|
/// the type name of each parameter (`None`: a generic, so any type)
|
|
104
104
|
selfMethods: HashMap<String, Vec<Option<String>>>,
|
|
105
105
|
/// name -> every method with a receiver of that name: what a call on an unknown
|
|
106
106
|
/// receiver may run. `methods` is the part of it that may be inlined or fused.
|
|
107
107
|
allMethods: HashMap<String, Vec<String>>,
|
|
108
|
-
/// `(Type, method)` -> the overrides a call on that type may run (`subtyping::overrideTargets`)
|
|
109
|
-
overrides: HashMap<(String, String), Vec<String>>,
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
fn keyOf(f: &Fn) -> String {
|
|
@@ -491,7 +489,6 @@ impl Driver {
|
|
|
491
489
|
recFields: HashMap::new(),
|
|
492
490
|
selfMethods: HashMap::new(),
|
|
493
491
|
allMethods: HashMap::new(),
|
|
494
|
-
overrides: crate::subtyping::overrideTargets(src),
|
|
495
492
|
};
|
|
496
493
|
let flat = crate::layout::flatStructs(&src.items);
|
|
497
494
|
for item in &src.items {
|
|
@@ -507,12 +504,6 @@ impl Driver {
|
|
|
507
504
|
d.selfMethods.insert(keyOf(f), params.collect());
|
|
508
505
|
d.allMethods.entry(f.name.clone()).or_default().push(keyOf(f));
|
|
509
506
|
}
|
|
510
|
-
// A method that may dispatch to an override is opaque: never inlined or fused.
|
|
511
|
-
if let Some(t) = &f.type_param {
|
|
512
|
-
if d.overrides.contains_key(&(t.clone(), f.name.clone())) {
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
507
|
// A `fip fun` stays a call, so the uniqueness proof sees every caller of it.
|
|
517
508
|
if f.is_extern || f.is_export || f.is_fip {
|
|
518
509
|
continue;
|
|
@@ -1602,16 +1593,13 @@ impl Driver {
|
|
|
1602
1593
|
out
|
|
1603
1594
|
}
|
|
1604
1595
|
|
|
1605
|
-
/// The fns a method call `object.name(..)` may run: the receiver type's own method
|
|
1596
|
+
/// The fns a method call `object.name(..)` may run: the receiver type's own method when
|
|
1606
|
-
/// every override below it when the receiver's type is known (`io.clear` is `Io.clear`),
|
|
1607
|
-
/// else every method of that name. Empty (an unknown callee) when there is none.
|
|
1597
|
+
/// the receiver's type is known (`io.clear` is `Io.clear`), else every method of that name. Empty (an unknown callee) when there is none.
|
|
1608
1598
|
fn methodKeys(&self, object: &Expr, name: &str) -> Vec<String> {
|
|
1609
1599
|
if let Some(t) = self.varType(object) {
|
|
1610
1600
|
let key = format!("{}.{}", t.name, name);
|
|
1611
1601
|
if self.selfMethods.contains_key(&key) {
|
|
1612
|
-
let mut keys = vec![key];
|
|
1613
|
-
keys.extend(self.overrides.get(&(t.name.clone(), name.to_string())).into_iter().flatten().cloned());
|
|
1614
|
-
return
|
|
1602
|
+
return vec![key];
|
|
1615
1603
|
}
|
|
1616
1604
|
}
|
|
1617
1605
|
self.allMethods.get(name).cloned().unwrap_or_default()
|
|
@@ -1628,7 +1616,7 @@ impl Driver {
|
|
|
1628
1616
|
|
|
1629
1617
|
/// Is `p`, the one candidate of the producer call `e` with the right result type, what
|
|
1630
1618
|
/// `e` runs? A method found by name alone is only if fusion sees every method of the name:
|
|
1631
|
-
/// one it cannot (
|
|
1619
|
+
/// one it cannot (extern) may have that result type too.
|
|
1632
1620
|
fn resolvedProducer(&self, e: &Expr, p: &Call) -> bool {
|
|
1633
1621
|
let ExprKind::Attribute(a) = &e.kind else { return true };
|
|
1634
1622
|
let AttrKind::Method(m) = &a.attr else { return true };
|
|
@@ -1647,7 +1635,7 @@ impl Driver {
|
|
|
1647
1635
|
fn onlyTaker(&self, c: &Call, k: usize, hint: &str) -> bool {
|
|
1648
1636
|
let takes = |params: &Vec<Option<String>>| {
|
|
1649
1637
|
params.len() == c.f.params.len()
|
|
1650
|
-
&& params[k - 1].as_ref().is_none_or(|t|
|
|
1638
|
+
&& params[k - 1].as_ref().is_none_or(|t| hint == t)
|
|
1651
1639
|
};
|
|
1652
1640
|
let others = self.allMethods.get(&c.f.name).into_iter().flatten().filter(|m| **m != c.key);
|
|
1653
1641
|
others.into_iter().all(|m| !self.selfMethods.get(m).is_some_and(takes))
|
|
@@ -1672,7 +1660,7 @@ impl Driver {
|
|
|
1672
1660
|
}
|
|
1673
1661
|
let recv = self.recvType(&a.object).or_else(|| self.resultName(&a.object));
|
|
1674
1662
|
let typed = recv.zip(c.f.type_param.as_ref());
|
|
1675
|
-
typed.is_some_and(|(t, owner)|
|
|
1663
|
+
typed.is_some_and(|(t, owner)| t == *owner)
|
|
1676
1664
|
|| self.allMethods.get(&m.name).is_some_and(|v| v.len() == 1)
|
|
1677
1665
|
}
|
|
1678
1666
|
|
|
@@ -1848,8 +1836,6 @@ impl Driver {
|
|
|
1848
1836
|
is_extern: false,
|
|
1849
1837
|
is_export: false,
|
|
1850
1838
|
is_test: false,
|
|
1851
|
-
is_override: false,
|
|
1852
|
-
is_abstract: false,
|
|
1853
1839
|
is_fip: false,
|
|
1854
1840
|
has_self: false,
|
|
1855
1841
|
params,
|
compiler/core/src/embeds.rs
DELETED
|
@@ -1,517 +0,0 @@
|
|
|
1
|
-
//! Record embedding and `override` checking.
|
|
2
|
-
//!
|
|
3
|
-
//! `struct Ship(Actor(x), s: Int)` embeds ONE struct, its base: the embedded struct's
|
|
4
|
-
//! fields are copied in (before the struct's own), its instance methods are copied, and
|
|
5
|
-
//! its `init` runs (as an `EmbedInit`, see `ctors.rs`) before the struct's own `init`.
|
|
6
|
-
//! A second embedded struct is an error.
|
|
7
|
-
//!
|
|
8
|
-
//! A method that comes from the embedded struct must be written `override fun`. A
|
|
9
|
-
//! replaced original stays reachable under the hidden name `<Owner>__<method>`;
|
|
10
|
-
//! `self.Owner.m(args)` is rewritten to call it.
|
|
11
|
-
//!
|
|
12
|
-
//! Runs from `lower::frontEnd` (public for direct use). Idempotent: a
|
|
13
|
-
//! processed record carries a marker entry in `embedFields`.
|
|
14
|
-
use std::collections::{HashMap, HashSet};
|
|
15
|
-
|
|
16
|
-
use crate::ast::*;
|
|
17
|
-
use crate::ctors::{fieldAssign, inferType, initStmts};
|
|
18
|
-
use crate::forms::mapExprs;
|
|
19
|
-
|
|
20
|
-
/// Marker in `embedFields` of a record this pass already handled (ignored by `lowerCtors`).
|
|
21
|
-
const DONE: &str = "#embedded";
|
|
22
|
-
|
|
23
|
-
struct Ctx {
|
|
24
|
-
done: HashSet<String>,
|
|
25
|
-
stack: Vec<String>,
|
|
26
|
-
/// Fields a record itself contributes when embedded: its header fields plus the
|
|
27
|
-
/// fields its `init` declares (`self.f: T = ..`); never the ones it embedded.
|
|
28
|
-
own: HashMap<String, Vec<EnumParam>>,
|
|
29
|
-
errors: Vec<String>,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
pub fn lowerEmbeds(source: &mut Source) -> Vec<String> {
|
|
33
|
-
let mut cx = Ctx {
|
|
34
|
-
done: HashSet::new(),
|
|
35
|
-
stack: Vec::new(),
|
|
36
|
-
own: HashMap::new(),
|
|
37
|
-
errors: Vec::new(),
|
|
38
|
-
};
|
|
39
|
-
let names: Vec<String> = source
|
|
40
|
-
.items
|
|
41
|
-
.iter()
|
|
42
|
-
.filter_map(|i| match i {
|
|
43
|
-
Item::Enum(e) => Some(e.name.clone()),
|
|
44
|
-
_ => None,
|
|
45
|
-
})
|
|
46
|
-
.collect();
|
|
47
|
-
for n in &names {
|
|
48
|
-
processRecord(source, n, &mut cx);
|
|
49
|
-
}
|
|
50
|
-
reportAbstractConstruction(source, &mut cx.errors);
|
|
51
|
-
// `override` on a free function.
|
|
52
|
-
for i in &source.items {
|
|
53
|
-
if let Item::Fn(f) = i {
|
|
54
|
-
if f.is_override && f.type_param.is_none() {
|
|
55
|
-
cx.errors.push(format!(
|
|
56
|
-
"{}: override is only allowed on a record instance method",
|
|
57
|
-
f.name
|
|
58
|
-
));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
let mut seen = HashSet::new();
|
|
63
|
-
cx.errors.retain(|e| seen.insert(e.clone()));
|
|
64
|
-
cx.errors
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fn enumIdx(source: &Source, name: &str) -> Option<usize> {
|
|
68
|
-
source
|
|
69
|
-
.items
|
|
70
|
-
.iter()
|
|
71
|
-
.position(|i| matches!(i, Item::Enum(e) if e.name == name))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
fn defines(source: &Source, rec: &str, name: &str) -> bool {
|
|
75
|
-
source.items.iter().any(
|
|
76
|
-
|i| matches!(i, Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == name),
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
fn instanceMethods(source: &Source, rec: &str) -> Vec<Fn> {
|
|
81
|
-
source
|
|
82
|
-
.items
|
|
83
|
-
.iter()
|
|
84
|
-
.filter_map(|i| match i {
|
|
85
|
-
Item::Fn(f)
|
|
86
|
-
if f.type_param.as_deref() == Some(rec) && f.has_self && f.name != "init" =>
|
|
87
|
-
{
|
|
88
|
-
Some(f.clone())
|
|
89
|
-
}
|
|
90
|
-
_ => None,
|
|
91
|
-
})
|
|
92
|
-
.collect()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/// An `abstract fun` instance method of a struct.
|
|
96
|
-
fn isAbstract(f: &Fn) -> bool {
|
|
97
|
-
f.has_self && f.is_abstract
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fn processRecord(source: &mut Source, rname: &str, cx: &mut Ctx) {
|
|
101
|
-
if cx.done.contains(rname) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if cx.stack.iter().any(|s| s == rname) {
|
|
105
|
-
cx.errors.push(format!("struct {} embeds itself", rname));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
let Some(idx) = enumIdx(source, rname) else { return };
|
|
109
|
-
cx.stack.push(rname.to_string());
|
|
110
|
-
let (isStruct, embeds, embedFields, headerNames, headerTys, headerMuts) =
|
|
111
|
-
match &source.items[idx] {
|
|
112
|
-
Item::Enum(e) => (
|
|
113
|
-
e.isStruct(),
|
|
114
|
-
e.structInfo().map(|i| i.embeds.clone()).unwrap_or_default(),
|
|
115
|
-
e.structInfo().map(|i| i.embedFields.clone()).unwrap_or_default(),
|
|
116
|
-
e.variants.first().map(|v| v.field_names.clone()).unwrap_or_default(),
|
|
117
|
-
e.variants.first().map(|v| v.fields.clone()).unwrap_or_default(),
|
|
118
|
-
e.variants.first().map(|v| v.mut_fields.clone()).unwrap_or_default(),
|
|
119
|
-
),
|
|
120
|
-
_ => unreachable!(),
|
|
121
|
-
};
|
|
122
|
-
let already = embedFields.iter().any(|(n, _)| n == DONE);
|
|
123
|
-
let hasHeader = !embedFields.is_empty() && !already;
|
|
124
|
-
let mut recEntries: Vec<(String, Vec<String>)> = embedFields
|
|
125
|
-
.iter()
|
|
126
|
-
.filter(|(n, _)| n != DONE && !embeds.contains(n))
|
|
127
|
-
.cloned()
|
|
128
|
-
.collect();
|
|
129
|
-
let mut spliced: Vec<EnumParam> = Vec::new();
|
|
130
|
-
// Record methods copied in, per source record: (owner, method).
|
|
131
|
-
let mut recMethods: Vec<(String, Fn)> = Vec::new();
|
|
132
|
-
let mut headerRecords: Vec<String> = Vec::new();
|
|
133
|
-
let mut seenField: Vec<(String, String)> = Vec::new();
|
|
134
|
-
|
|
135
|
-
if hasHeader {
|
|
136
|
-
// 1. Header entries, in order.
|
|
137
|
-
let entries: Vec<(String, Vec<String>)> = embedFields
|
|
138
|
-
.iter()
|
|
139
|
-
.filter(|(n, _)| embeds.contains(n))
|
|
140
|
-
.cloned()
|
|
141
|
-
.collect();
|
|
142
|
-
if let [first, second, ..] = &entries[..] {
|
|
143
|
-
cx.errors.push(format!(
|
|
144
|
-
"struct {} embeds {} and {}; a struct can embed only one struct \u{2014} use a field ({}: {})",
|
|
145
|
-
rname,
|
|
146
|
-
first.0,
|
|
147
|
-
second.0,
|
|
148
|
-
second.0.to_lowercase(),
|
|
149
|
-
second.0
|
|
150
|
-
));
|
|
151
|
-
}
|
|
152
|
-
for (ename, exposed) in entries.iter().take(1) {
|
|
153
|
-
let Some(eidx) = enumIdx(source, ename) else {
|
|
154
|
-
cx.errors.push(format!("type {} cannot embed {}: it is not a struct", rname, ename));
|
|
155
|
-
continue;
|
|
156
|
-
};
|
|
157
|
-
// Embedding works on any struct, whatever its layout.
|
|
158
|
-
let (eStruct, eGeneric) = match &source.items[eidx] {
|
|
159
|
-
Item::Enum(e) => (e.isStruct(), !e.generics.is_empty()),
|
|
160
|
-
_ => unreachable!(),
|
|
161
|
-
};
|
|
162
|
-
if !eStruct {
|
|
163
|
-
cx.errors.push(format!("type {} cannot embed {}: it is not a struct", rname, ename));
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
if !isStruct {
|
|
167
|
-
cx.errors.push(format!(
|
|
168
|
-
"type {} cannot embed struct {}; only structs embed structs",
|
|
169
|
-
rname, ename
|
|
170
|
-
));
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if eGeneric {
|
|
174
|
-
cx.errors
|
|
175
|
-
.push(format!("cannot embed generic struct {}", ename));
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
processRecord(source, ename, cx);
|
|
179
|
-
if !cx.done.contains(ename) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
// E's processed state: its chain of embedded structs.
|
|
183
|
-
let eChain: Vec<String> = match &source.items[eidx] {
|
|
184
|
-
Item::Enum(e) => e
|
|
185
|
-
.embedFields()
|
|
186
|
-
.iter()
|
|
187
|
-
.filter(|(n, _)| n != DONE)
|
|
188
|
-
.map(|(n, _)| n.clone())
|
|
189
|
-
.collect(),
|
|
190
|
-
_ => unreachable!(),
|
|
191
|
-
};
|
|
192
|
-
let mut chain = eChain;
|
|
193
|
-
chain.push(ename.clone());
|
|
194
|
-
// Route each selected name to the chain struct that owns it.
|
|
195
|
-
let mut recExp: HashMap<String, Vec<String>> = HashMap::new();
|
|
196
|
-
for n in exposed {
|
|
197
|
-
if let Some(c) = chain
|
|
198
|
-
.iter()
|
|
199
|
-
.find(|c| cx.own.get(*c).is_some_and(|f| f.iter().any(|p| p.name == *n)))
|
|
200
|
-
{
|
|
201
|
-
recExp.entry(c.clone()).or_default().push(n.clone());
|
|
202
|
-
} else {
|
|
203
|
-
cx.errors
|
|
204
|
-
.push(format!("struct {} has no field {}", ename, n));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
for c in &chain {
|
|
208
|
-
if recEntries.iter().any(|(n, _)| n == c) {
|
|
209
|
-
cx.errors
|
|
210
|
-
.push(format!("struct {} embeds {} more than once", rname, c));
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
recEntries.push((
|
|
214
|
-
c.clone(),
|
|
215
|
-
recExp.get(c).cloned().unwrap_or_default(),
|
|
216
|
-
));
|
|
217
|
-
for p in cx.own.get(c).cloned().unwrap_or_default() {
|
|
218
|
-
if let Some((_, other)) = seenField.iter().find(|(n, _)| *n == p.name) {
|
|
219
|
-
cx.errors.push(format!(
|
|
220
|
-
"{} gets field {} from both {} and {}",
|
|
221
|
-
rname, p.name, other, c
|
|
222
|
-
));
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if headerNames.contains(&p.name) {
|
|
226
|
-
cx.errors.push(format!(
|
|
227
|
-
"struct {} field {} is already declared by struct {}",
|
|
228
|
-
rname, p.name, c
|
|
229
|
-
));
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
seenField.push((p.name.clone(), c.clone()));
|
|
233
|
-
spliced.push(p);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
headerRecords.push(ename.clone());
|
|
237
|
-
for m in instanceMethods(source, ename) {
|
|
238
|
-
recMethods.push((ename.clone(), m));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 3. The record's own methods, before anything is copied in.
|
|
244
|
-
let ownMethods: Vec<Fn> = source
|
|
245
|
-
.items
|
|
246
|
-
.iter()
|
|
247
|
-
.filter_map(|i| match i {
|
|
248
|
-
Item::Fn(f) if f.type_param.as_deref() == Some(rname) => Some(f.clone()),
|
|
249
|
-
_ => None,
|
|
250
|
-
})
|
|
251
|
-
.collect();
|
|
252
|
-
|
|
253
|
-
// Providers: the embedded struct's instance methods, (method, owner).
|
|
254
|
-
let mut providers: Vec<(String, String)> = Vec::new();
|
|
255
|
-
for (owner, m) in &recMethods {
|
|
256
|
-
if !providers.iter().any(|(n, _)| *n == m.name) {
|
|
257
|
-
providers.push((m.name.clone(), owner.clone()));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if !already {
|
|
261
|
-
for f in &ownMethods {
|
|
262
|
-
if f.name == "init" && f.has_self {
|
|
263
|
-
if f.is_override {
|
|
264
|
-
cx.errors.push(format!("{}.init overrides nothing", rname));
|
|
265
|
-
}
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if f.is_override && !f.has_self {
|
|
269
|
-
cx.errors.push(format!(
|
|
270
|
-
"{}.{}: override is only allowed on an instance method",
|
|
271
|
-
rname, f.name
|
|
272
|
-
));
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if !f.has_self {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
let found = providers.iter().find(|(n, _)| *n == f.name);
|
|
279
|
-
match (found, f.is_override) {
|
|
280
|
-
(Some((_, owner)), false) => cx.errors.push(format!(
|
|
281
|
-
"{}.{} hides {}.{}; mark it override",
|
|
282
|
-
rname, f.name, owner, f.name
|
|
283
|
-
)),
|
|
284
|
-
(None, true) => cx
|
|
285
|
-
.errors
|
|
286
|
-
.push(format!("{}.{} overrides nothing", rname, f.name)),
|
|
287
|
-
_ => {}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Every abstract method of an embedded struct needs an `override` with a body here.
|
|
293
|
-
if !already {
|
|
294
|
-
for (owner, m) in &recMethods {
|
|
295
|
-
if !isAbstract(m) {
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
let implemented = ownMethods
|
|
299
|
-
.iter()
|
|
300
|
-
.any(|f| f.name == m.name && f.has_self && !isAbstract(f));
|
|
301
|
-
if !implemented {
|
|
302
|
-
cx.errors.push(format!(
|
|
303
|
-
"struct {} embeds {} but does not override abstract method {}",
|
|
304
|
-
rname, owner, m.name
|
|
305
|
-
));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// 4. Copy embedded records' methods; replaced ones go under `<Owner>__<m>`.
|
|
311
|
-
let mut copies: Vec<Fn> = Vec::new();
|
|
312
|
-
let mut copiedFrom: Vec<(String, String)> = Vec::new(); // (method, owner)
|
|
313
|
-
for (owner, m) in &recMethods {
|
|
314
|
-
if isAbstract(m) {
|
|
315
|
-
// Never copied: the embedder overrides it (or has been reported above).
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
let ownDefines = ownMethods.iter().any(|f| f.name == m.name && f.has_self);
|
|
319
|
-
let mut c = m.clone();
|
|
320
|
-
refreshFnIds(&mut c);
|
|
321
|
-
c.type_param = Some(rname.to_string());
|
|
322
|
-
if ownDefines {
|
|
323
|
-
c.name = format!("{}__{}", owner, m.name);
|
|
324
|
-
c.is_override = false;
|
|
325
|
-
if !defines(source, rname, &c.name) && !copies.iter().any(|x| x.name == c.name) {
|
|
326
|
-
copies.push(c);
|
|
327
|
-
}
|
|
328
|
-
} else if let Some((_, other)) = copiedFrom.iter().find(|(n, _)| *n == m.name) {
|
|
329
|
-
cx.errors.push(format!(
|
|
330
|
-
"{} gets method {} from both {} and {}; define {}.{} with override",
|
|
331
|
-
rname, m.name, other, owner, rname, m.name
|
|
332
|
-
));
|
|
333
|
-
} else if !defines(source, rname, &m.name) {
|
|
334
|
-
copiedFrom.push((m.name.clone(), owner.clone()));
|
|
335
|
-
copies.push(c);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// 6. `self.X.m(args)` in the record's own methods.
|
|
340
|
-
if !already {
|
|
341
|
-
let ownDefined: HashSet<String> = ownMethods.iter().map(|f| f.name.clone()).collect();
|
|
342
|
-
let mut errs: Vec<String> = Vec::new();
|
|
343
|
-
for it in source.items.iter_mut() {
|
|
344
|
-
let Item::Fn(f) = it else { continue };
|
|
345
|
-
if f.type_param.as_deref() != Some(rname) {
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
let fname = f.name.clone();
|
|
349
|
-
let mut calls: Vec<String> = Vec::new();
|
|
350
|
-
mapExprs(&mut f.body, &mut |e| {
|
|
351
|
-
let ExprKind::Attribute(a) = &mut e.kind else { return };
|
|
352
|
-
let AttrKind::Method(call) = &a.attr else { return };
|
|
353
|
-
let ExprKind::Attribute(inner) = &a.object.kind else { return };
|
|
354
|
-
let (Expr { kind: ExprKind::Self_, .. }, AttrKind::Field(x)) = (&inner.object, &inner.attr) else {
|
|
355
|
-
return;
|
|
356
|
-
};
|
|
357
|
-
if !x.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
let m = call.name.clone();
|
|
361
|
-
let res: Result<String, String> = if headerRecords.iter().any(|r| r == x) {
|
|
362
|
-
if recMethods.iter().any(|(o, mm)| o == x && mm.name == m) {
|
|
363
|
-
if ownDefined.contains(&m) {
|
|
364
|
-
Ok(format!("{}__{}", x, m))
|
|
365
|
-
} else {
|
|
366
|
-
Ok(m.clone())
|
|
367
|
-
}
|
|
368
|
-
} else {
|
|
369
|
-
Err(format!(
|
|
370
|
-
"{}.{} calls self.{}.{}, but {} has no method {}",
|
|
371
|
-
rname, fname, x, m, x, m
|
|
372
|
-
))
|
|
373
|
-
}
|
|
374
|
-
} else {
|
|
375
|
-
Err(format!(
|
|
376
|
-
"{}.{} calls self.{}.{}, but {} does not embed {}",
|
|
377
|
-
rname, fname, x, m, rname, x
|
|
378
|
-
))
|
|
379
|
-
};
|
|
380
|
-
match &res {
|
|
381
|
-
Ok(newName) => {
|
|
382
|
-
let call = call.clone();
|
|
383
|
-
*e = Expr::Attribute(Box::new(AttributeExpr {
|
|
384
|
-
object: Expr::Self_,
|
|
385
|
-
attr: AttrKind::Method(FnCall {
|
|
386
|
-
name: newName.clone(),
|
|
387
|
-
..call
|
|
388
|
-
}),
|
|
389
|
-
}));
|
|
390
|
-
}
|
|
391
|
-
Err(msg) => calls.push(msg.clone()),
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
errs.extend(calls);
|
|
395
|
-
}
|
|
396
|
-
cx.errors.extend(errs);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// 7. Write back.
|
|
400
|
-
for it in std::mem::take(&mut copies) {
|
|
401
|
-
source.items.push(Item::Fn(it));
|
|
402
|
-
}
|
|
403
|
-
// Contribution when embedded: header plus init-declared fields.
|
|
404
|
-
if !already {
|
|
405
|
-
let mut own: Vec<EnumParam> = headerNames
|
|
406
|
-
.iter()
|
|
407
|
-
.zip(headerTys.iter())
|
|
408
|
-
.enumerate()
|
|
409
|
-
.map(|(i, (n, t))| EnumParam {
|
|
410
|
-
name: n.clone(),
|
|
411
|
-
ty: t.clone(),
|
|
412
|
-
isMut: headerMuts.get(i).copied().unwrap_or(false),
|
|
413
|
-
})
|
|
414
|
-
.collect();
|
|
415
|
-
let init = source.items.iter().find_map(|i| match i {
|
|
416
|
-
Item::Fn(f)
|
|
417
|
-
if f.type_param.as_deref() == Some(rname) && f.name == "init" && f.has_self =>
|
|
418
|
-
{
|
|
419
|
-
Some(f.clone())
|
|
420
|
-
}
|
|
421
|
-
_ => None,
|
|
422
|
-
});
|
|
423
|
-
if let Some(init) = &init {
|
|
424
|
-
let known: HashSet<String> = own
|
|
425
|
-
.iter()
|
|
426
|
-
.map(|p| p.name.clone())
|
|
427
|
-
.chain(spliced.iter().map(|p| p.name.clone()))
|
|
428
|
-
.collect();
|
|
429
|
-
for st in initStmts(init) {
|
|
430
|
-
let Some((fname, val, ann)) = fieldAssign(&st) else { continue };
|
|
431
|
-
if known.contains(fname) || own.iter().any(|p| p.name == fname) {
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
if let Some(t) = ann.cloned().or_else(|| inferType(val)) {
|
|
435
|
-
own.push(EnumParam {
|
|
436
|
-
name: fname.to_string(),
|
|
437
|
-
isMut: false,
|
|
438
|
-
ty: t,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
cx.own.insert(rname.to_string(), own);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if hasHeader {
|
|
447
|
-
if let Item::Enum(e) = &mut source.items[idx] {
|
|
448
|
-
let mut fields: Vec<(String, Vec<String>)> = recEntries.iter().cloned().collect();
|
|
449
|
-
fields.push((DONE.to_string(), vec![]));
|
|
450
|
-
if let Some(info) = e.structInfoMut() {
|
|
451
|
-
info.embeds = Vec::new();
|
|
452
|
-
info.embedFields = fields;
|
|
453
|
-
}
|
|
454
|
-
if let Some(v) = e.variants.first_mut() {
|
|
455
|
-
let mut tys: Vec<Type> = spliced.iter().map(|p| p.ty.clone()).collect();
|
|
456
|
-
tys.extend(std::mem::take(&mut v.fields));
|
|
457
|
-
let mut names: Vec<String> = spliced.iter().map(|p| p.name.clone()).collect();
|
|
458
|
-
names.extend(std::mem::take(&mut v.field_names));
|
|
459
|
-
let mut muts: Vec<bool> = spliced.iter().map(|p| p.isMut).collect();
|
|
460
|
-
let own = std::mem::take(&mut v.mut_fields);
|
|
461
|
-
muts.extend(own.into_iter().chain(std::iter::repeat(false)).take(tys.len() - muts.len()));
|
|
462
|
-
v.fields = tys;
|
|
463
|
-
v.field_names = names;
|
|
464
|
-
v.mut_fields = muts;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
cx.stack.pop();
|
|
469
|
-
cx.done.insert(rname.to_string());
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/// `B(...)` where `B` has an `abstract fun` method: B is abstract.
|
|
473
|
-
fn reportAbstractConstruction(source: &mut Source, errors: &mut Vec<String>) {
|
|
474
|
-
let mut abstractOf: HashMap<String, String> = HashMap::new();
|
|
475
|
-
for i in &source.items {
|
|
476
|
-
if let Item::Fn(f) = i {
|
|
477
|
-
if let Some(t) = &f.type_param {
|
|
478
|
-
if isAbstract(f) && !abstractOf.contains_key(t) {
|
|
479
|
-
abstractOf.insert(t.clone(), f.name.clone());
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
if abstractOf.is_empty() {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
let mut found: Vec<String> = Vec::new();
|
|
488
|
-
let mut visit = |e: &mut Expr| {
|
|
489
|
-
if let ExprKind::ClassCall(c) = &mut e.kind {
|
|
490
|
-
if let Some(m) = abstractOf.get(&c.type_name) {
|
|
491
|
-
found.push(format!("cannot construct {}: {} has no body", c.type_name, m));
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
for it in source.items.iter_mut() {
|
|
496
|
-
match it {
|
|
497
|
-
Item::Fn(f) => {
|
|
498
|
-
mapExprs(&mut f.body, &mut visit);
|
|
499
|
-
for p in f.params.iter_mut() {
|
|
500
|
-
if let Some(d) = &mut p.default {
|
|
501
|
-
crate::forms::mapExprsIn(d, &mut visit);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
Item::Const(c) => crate::forms::mapExprsIn(&mut c.value, &mut visit),
|
|
506
|
-
Item::Test(t) => {
|
|
507
|
-
let mut body = FnBody::Block(std::mem::replace(&mut t.body, Block { stmts: vec![] }));
|
|
508
|
-
mapExprs(&mut body, &mut visit);
|
|
509
|
-
if let FnBody::Block(b) = body {
|
|
510
|
-
t.body = b;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
_ => {}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
errors.extend(found);
|
|
517
|
-
}
|
compiler/core/src/layout.rs
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
//! The one place a struct's runtime layout is decided.
|
|
2
2
|
//!
|
|
3
3
|
//! A struct is `Flat` (today's `struct` layout: unboxed, copied field-wise)
|
|
4
|
-
//! when it has fields but no generics, no
|
|
4
|
+
//! when it has fields but no generics, no `mut` field, is not recursive and
|
|
5
5
|
//! every field is flat-able (a scalar primitive or another flat struct).
|
|
6
6
|
//! Everything else is `Boxed` (a heap struct). A
|
|
7
7
|
//! struct with a `fun init(self)` method (which may declare internal fields)
|
|
8
8
|
//! is always `Boxed`; the parser records it in `StructInfo::forceBoxed`.
|
|
9
9
|
//! A struct generated from an inline variant is always `Boxed` too.
|
|
10
|
-
//! A struct that another struct uses as its `base` (first embed) is `Boxed`:
|
|
11
|
-
//! its subtypes are wasm-gc subtypes of its heap struct.
|
|
12
10
|
|
|
13
11
|
use crate::ast::{Enum, Item};
|
|
14
12
|
use std::collections::{HashMap, HashSet};
|
|
@@ -26,8 +24,7 @@ const FLAT_SCALARS: [&str; 4] = ["Int", "Float", "Bool", "Byte"];
|
|
|
26
24
|
/// (used to resolve fields naming other structs).
|
|
27
25
|
pub fn structLayout(e: &Enum, all: &[Enum]) -> Layout {
|
|
28
26
|
let find = |n: &str| all.iter().find(|o| o.name == n);
|
|
29
|
-
let bases = basesOf(all.iter());
|
|
30
|
-
if isFlat(e, &find, &
|
|
27
|
+
if isFlat(e, &find, &mut Vec::new()) {
|
|
31
28
|
Layout::Flat
|
|
32
29
|
} else {
|
|
33
30
|
Layout::Boxed
|
|
@@ -37,8 +34,7 @@ pub fn structLayout(e: &Enum, all: &[Enum]) -> Layout {
|
|
|
37
34
|
/// [`structLayout`] over a program's items.
|
|
38
35
|
pub fn itemLayout(e: &Enum, items: &[Item]) -> Layout {
|
|
39
36
|
let find = |n: &str| enumIn(items, n);
|
|
40
|
-
let bases = basesOf(enums(items));
|
|
41
|
-
if isFlat(e, &find, &
|
|
37
|
+
if isFlat(e, &find, &mut Vec::new()) {
|
|
42
38
|
Layout::Flat
|
|
43
39
|
} else {
|
|
44
40
|
Layout::Boxed
|
|
@@ -64,10 +60,9 @@ pub fn flatStructs(items: &[Item]) -> HashSet<String> {
|
|
|
64
60
|
}
|
|
65
61
|
}
|
|
66
62
|
let find = |n: &str| byName.get(n).copied();
|
|
67
|
-
let bases = basesOf(enums(items));
|
|
68
63
|
byName
|
|
69
64
|
.values()
|
|
70
|
-
.filter(|e| isFlat(e, &find, &
|
|
65
|
+
.filter(|e| isFlat(e, &find, &mut Vec::new()))
|
|
71
66
|
.map(|e| e.name.clone())
|
|
72
67
|
.collect()
|
|
73
68
|
}
|
|
@@ -79,35 +74,19 @@ fn enumIn<'a>(items: &'a [Item], name: &str) -> Option<&'a Enum> {
|
|
|
79
74
|
})
|
|
80
75
|
}
|
|
81
76
|
|
|
82
|
-
fn enums(items: &[Item]) -> impl Iterator<Item = &Enum> {
|
|
83
|
-
items.iter().filter_map(|i| match i {
|
|
84
|
-
Item::Enum(e) => Some(e),
|
|
85
|
-
_ => None,
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// Names some struct uses as its base (first embed).
|
|
90
|
-
fn basesOf<'a>(all: impl IntoIterator<Item = &'a Enum>) -> HashSet<String> {
|
|
91
|
-
all.into_iter()
|
|
92
|
-
.filter(|e| e.isStruct())
|
|
93
|
-
.filter_map(|e| e.base().map(str::to_string))
|
|
94
|
-
.collect()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
77
|
fn isFlat<'a, F>(
|
|
98
78
|
e: &'a Enum,
|
|
99
79
|
find: &F,
|
|
100
|
-
bases: &HashSet<String>,
|
|
101
80
|
visiting: &mut Vec<&'a str>,
|
|
102
81
|
) -> bool
|
|
103
82
|
where
|
|
104
83
|
F: Fn(&str) -> Option<&'a Enum>,
|
|
105
84
|
{
|
|
106
85
|
let Some(info) = e.structInfo() else { return false };
|
|
107
|
-
if info.forceBoxed || e.variants.len() != 1
|
|
86
|
+
if info.forceBoxed || e.variants.len() != 1 {
|
|
108
87
|
return false;
|
|
109
88
|
}
|
|
110
|
-
if !e.generics.is_empty()
|
|
89
|
+
if !e.generics.is_empty() {
|
|
111
90
|
return false;
|
|
112
91
|
}
|
|
113
92
|
let v = &e.variants[0];
|
|
@@ -123,7 +102,7 @@ where
|
|
|
123
102
|
let ok = v.fields.iter().all(|t| {
|
|
124
103
|
t.generics.is_empty()
|
|
125
104
|
&& (FLAT_SCALARS.contains(&t.name.as_str())
|
|
126
|
-
|| find(&t.name).is_some_and(|o| isFlat(o, find,
|
|
105
|
+
|| find(&t.name).is_some_and(|o| isFlat(o, find, visiting)))
|
|
127
106
|
});
|
|
128
107
|
visiting.pop();
|
|
129
108
|
ok
|
compiler/core/src/lib.rs
CHANGED
|
@@ -9,7 +9,6 @@ pub mod closure_spec;
|
|
|
9
9
|
pub mod ctors;
|
|
10
10
|
pub mod diag;
|
|
11
11
|
pub mod drive;
|
|
12
|
-
pub mod embeds;
|
|
13
12
|
pub mod enum_checks;
|
|
14
13
|
pub mod formatter;
|
|
15
14
|
pub mod fip;
|
|
@@ -26,7 +25,6 @@ pub mod rename;
|
|
|
26
25
|
pub mod reuse;
|
|
27
26
|
pub mod session;
|
|
28
27
|
pub mod simplify;
|
|
29
|
-
pub mod subtyping;
|
|
30
28
|
pub mod tail;
|
|
31
29
|
pub mod tests_lower;
|
|
32
30
|
pub mod trmc;
|
compiler/core/src/lower.rs
CHANGED
|
@@ -20,15 +20,11 @@ pub fn perFileDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
|
|
|
20
20
|
Ok(())
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
/// Front-end lowering of a merged program:
|
|
23
|
+
/// Front-end lowering of a merged program: constructors, inline-variant
|
|
24
24
|
/// type checks, union-enum errors, then forms. The first failing stage returns its
|
|
25
25
|
/// errors.
|
|
26
26
|
pub fn frontEndDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
|
|
27
27
|
crate::session::begin();
|
|
28
|
-
let embedErrors = crate::embeds::lowerEmbeds(source);
|
|
29
|
-
if !embedErrors.is_empty() {
|
|
30
|
-
return Err(diags(DiagKind::Type, embedErrors));
|
|
31
|
-
}
|
|
32
28
|
let ctorErrors = crate::ctors::lowerCtors(source);
|
|
33
29
|
if !ctorErrors.is_empty() {
|
|
34
30
|
return Err(diags(DiagKind::Type, ctorErrors));
|
|
@@ -42,7 +38,7 @@ pub fn frontEndDiag(source: &mut Source) -> Result<(), Vec<Diagnostic>> {
|
|
|
42
38
|
return Err(diags(DiagKind::Type, memberErrors));
|
|
43
39
|
}
|
|
44
40
|
crate::forms::prepareForms(source);
|
|
45
|
-
// The stages above build nodes (
|
|
41
|
+
// The stages above build nodes (constructors) with no id.
|
|
46
42
|
crate::ast::numberIds(source);
|
|
47
43
|
Ok(())
|
|
48
44
|
}
|
compiler/core/src/parser.rs
CHANGED
|
@@ -307,8 +307,8 @@ impl<'a> AstParser<'a> {
|
|
|
307
307
|
// struct: "struct" name generics? ("(" field,* ")")? ("=" body)?
|
|
308
308
|
// Its layout is chosen by `layout::structLayout`.
|
|
309
309
|
let mut e = self.parseCtor(node); // always a `TypeKind::Struct`
|
|
310
|
-
//
|
|
310
|
+
// `fun init(self)` (internal fields) needs a boxed struct; lowering later
|
|
311
|
-
//
|
|
311
|
+
// removes the init, so record it here.
|
|
312
312
|
let hasInit = self
|
|
313
313
|
.childrenOfKind(node, "fn")
|
|
314
314
|
.into_iter()
|
|
@@ -317,7 +317,7 @@ impl<'a> AstParser<'a> {
|
|
|
317
317
|
f.name == "init" && f.has_self
|
|
318
318
|
});
|
|
319
319
|
if let Some(info) = e.structInfoMut() {
|
|
320
|
-
info.forceBoxed =
|
|
320
|
+
info.forceBoxed = hasInit;
|
|
321
321
|
}
|
|
322
322
|
e
|
|
323
323
|
}
|
|
@@ -331,27 +331,9 @@ impl<'a> AstParser<'a> {
|
|
|
331
331
|
let generics = self.parseGenericsField(node);
|
|
332
332
|
// The enum_named_field children, in order, are the constructor's fields.
|
|
333
333
|
let variant = self.parseEnumVariant(node);
|
|
334
|
-
let mut cursor = node.walk();
|
|
335
|
-
let embedFields: Vec<(String, Vec<String>)> = node
|
|
336
|
-
.children_by_field_name("embed", &mut cursor)
|
|
337
|
-
.map(|n| {
|
|
338
|
-
let name = n
|
|
339
|
-
.child_by_field_name("name")
|
|
340
|
-
.map(|c| self.text(c))
|
|
341
|
-
.unwrap_or_default();
|
|
342
|
-
let mut c2 = n.walk();
|
|
343
|
-
let exposed = n
|
|
344
|
-
.children_by_field_name("exposed", &mut c2)
|
|
345
|
-
.map(|c| self.text(c))
|
|
346
|
-
.collect();
|
|
347
|
-
(name, exposed)
|
|
348
|
-
})
|
|
349
|
-
.collect();
|
|
350
|
-
let embeds: Vec<String> = embedFields.iter().map(|(n, _)| n.clone()).collect();
|
|
351
|
-
let base = embeds.first().cloned();
|
|
352
334
|
let pos = node.start_position();
|
|
353
335
|
Enum {
|
|
354
|
-
kind: TypeKind::Struct(StructInfo { forceBoxed: false
|
|
336
|
+
kind: TypeKind::Struct(StructInfo { forceBoxed: false }),
|
|
355
337
|
name,
|
|
356
338
|
line: pos.row + 1,
|
|
357
339
|
column: pos.column + 1,
|
|
@@ -479,8 +461,6 @@ impl<'a> AstParser<'a> {
|
|
|
479
461
|
|
|
480
462
|
let is_extern = node.child_by_field_name("externKw").is_some();
|
|
481
463
|
let is_export = node.child_by_field_name("exportKw").is_some();
|
|
482
|
-
let is_override = node.child_by_field_name("overrideKw").is_some();
|
|
483
|
-
let is_abstract = node.child_by_field_name("abstractKw").is_some();
|
|
484
464
|
let is_fip = node.child_by_field_name("fipKw").is_some();
|
|
485
465
|
|
|
486
466
|
let generics: Vec<String> = node
|
|
@@ -504,8 +484,6 @@ impl<'a> AstParser<'a> {
|
|
|
504
484
|
is_extern,
|
|
505
485
|
is_export,
|
|
506
486
|
is_test: false,
|
|
507
|
-
is_override,
|
|
508
|
-
is_abstract,
|
|
509
487
|
is_fip,
|
|
510
488
|
has_self: !self.childrenOfKind(node, "self").is_empty(),
|
|
511
489
|
params,
|
|
@@ -1890,7 +1868,6 @@ mod removedSyntaxTests {
|
|
|
1890
1868
|
fn structAcceptsRecordCapabilities() {
|
|
1891
1869
|
let cases = [
|
|
1892
1870
|
("Box", "struct Box[T](value: T)\n"),
|
|
1893
|
-
("House", "struct Actor(x: Int)\nstruct House(Actor(x), k: Int)\n"),
|
|
1894
1871
|
("Node", "struct Node(mut count: Int)\n"),
|
|
1895
1872
|
("Io", "struct Io\n"),
|
|
1896
1873
|
("Io", "struct Io() =\n fun one(self) -> Int = 1\n"),
|
compiler/core/src/purity.rs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//! So is `x.f(..)` where `f` may be an fn-typed field. A method call on `self`, on a
|
|
9
9
|
//! parameter of a named type (never rebound or shadowed) or on a field read of one
|
|
10
10
|
//! resolves by that type (`io.clear(..)` is `Io.clear`, not `Slice.clear`): to the type's
|
|
11
|
-
//! method
|
|
11
|
+
//! method.
|
|
12
12
|
//! A method call whose receiver type is unknown is pure only if every method of that name
|
|
13
13
|
//! is. Solved as a greatest fixpoint, so (mutual) recursion with no impure effect stays pure.
|
|
14
14
|
use crate::ast::*;
|
|
@@ -53,12 +53,6 @@ pub fn pureFns(src: &Source) -> HashSet<String> {
|
|
|
53
53
|
if reachesIo(f, &ioTypes) {
|
|
54
54
|
info.impure = true;
|
|
55
55
|
}
|
|
56
|
-
// A call to `Base.m` may run any overrider: opaque, so never assumed pure.
|
|
57
|
-
if let Some(t) = &f.type_param {
|
|
58
|
-
if receivers.overrides.contains_key(&(t.clone(), f.name.clone())) {
|
|
59
|
-
info.impure = true;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
56
|
infos.push(info);
|
|
63
57
|
}
|
|
64
58
|
// (type, name) -> index, for `Type.m(..)` calls.
|
|
@@ -249,8 +243,6 @@ struct Info {
|
|
|
249
243
|
struct Receivers<'a> {
|
|
250
244
|
/// struct -> field -> the name of its type; a field typed by the struct's generic is left out
|
|
251
245
|
fields: HashMap<&'a str, HashMap<&'a str, &'a str>>,
|
|
252
|
-
/// `(Type, method)` -> the overrides such a call may run
|
|
253
|
-
overrides: HashMap<(String, String), Vec<String>>,
|
|
254
246
|
/// names of the fn-typed fields of all types: `x.f(..)` may call that closure
|
|
255
247
|
fnFields: HashSet<&'a str>,
|
|
256
248
|
/// `Type.method` of every method: on such a receiver the method wins over a field
|
|
@@ -278,7 +270,7 @@ impl<'a> Receivers<'a> {
|
|
|
278
270
|
let known = v.field_names.iter().zip(&v.fields).filter(|(_, t)| !e.generics.iter().any(|g| g.name == t.name));
|
|
279
271
|
fields.insert(e.name.as_str(), known.map(|(n, t)| (n.as_str(), t.name.as_str())).collect());
|
|
280
272
|
}
|
|
281
|
-
Receivers { fields,
|
|
273
|
+
Receivers { fields, fnFields, methods }
|
|
282
274
|
}
|
|
283
275
|
}
|
|
284
276
|
|
|
@@ -523,10 +515,7 @@ impl<'a> Walker<'a> {
|
|
|
523
515
|
self.mutates = true;
|
|
524
516
|
}
|
|
525
517
|
Some(t) => {
|
|
526
|
-
let below = self.receivers.overrides.get(&(t.clone(), c.name.clone()));
|
|
527
|
-
let keys = below.into_iter().flatten().cloned();
|
|
528
|
-
|
|
518
|
+
self.calls.push(Call::Typed(format!("{}.{}", t, c.name), c.name.clone()));
|
|
529
|
-
self.calls.extend(calls);
|
|
530
519
|
}
|
|
531
520
|
None => self.calls.push(Call::Method(c.name.clone())),
|
|
532
521
|
},
|
compiler/core/src/subtyping.rs
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
//! Struct subtyping: a struct embedding another struct FIRST is its subtype.
|
|
2
|
-
//!
|
|
3
|
-
//! The base is recorded by the parser in `StructInfo::base`. Only a non-generic
|
|
4
|
-
//! struct item counts as a base (traits and generic structs never do).
|
|
5
|
-
|
|
6
|
-
use crate::ast::{Enum, Item, Source};
|
|
7
|
-
use std::collections::{HashMap, HashSet};
|
|
8
|
-
|
|
9
|
-
fn structNamed<'a>(source: &'a Source, name: &str) -> Option<&'a Enum> {
|
|
10
|
-
source.items.iter().find_map(|i| match i {
|
|
11
|
-
Item::Enum(e) if e.name == name && e.isStruct() => Some(e),
|
|
12
|
-
_ => None,
|
|
13
|
-
})
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/// The struct `name` subtypes directly, if its first embed is a valid base.
|
|
17
|
-
fn baseOf<'a>(source: &'a Source, name: &str) -> Option<&'a str> {
|
|
18
|
-
let b = structNamed(source, name)?.base()?;
|
|
19
|
-
let be = structNamed(source, b)?;
|
|
20
|
-
be.generics.is_empty().then_some(b)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/// Strict ancestors of `name`, nearest first (cycle-safe).
|
|
24
|
-
fn ancestors<'a>(source: &'a Source, name: &str) -> Vec<&'a str> {
|
|
25
|
-
let mut out: Vec<&str> = Vec::new();
|
|
26
|
-
let mut cur = name;
|
|
27
|
-
while let Some(b) = baseOf(source, cur) {
|
|
28
|
-
if b == name || out.contains(&b) {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
out.push(b);
|
|
32
|
-
cur = b;
|
|
33
|
-
}
|
|
34
|
-
out
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// Is `a` the same struct as `b` or a (transitive) subtype of it?
|
|
38
|
-
pub fn isSubtype(source: &Source, a: &str, b: &str) -> bool {
|
|
39
|
-
a == b || ancestors(source, a).contains(&b)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fn declaresOverride(source: &Source, s: &str, m: &str) -> bool {
|
|
43
|
-
source.items.iter().any(|i| match i {
|
|
44
|
-
Item::Fn(f) => f.is_override && f.name == m && f.type_param.as_deref() == Some(s),
|
|
45
|
-
_ => false,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/// Strict subtypes of `base` whose own method `m` is an `override`, deepest first.
|
|
50
|
-
pub fn overriders(source: &Source, base: &str, m: &str) -> Vec<String> {
|
|
51
|
-
let mut found: Vec<(usize, String)> = Vec::new();
|
|
52
|
-
for i in &source.items {
|
|
53
|
-
let Item::Enum(e) = i else { continue };
|
|
54
|
-
if e.name == base || !isSubtype(source, &e.name, base) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
if declaresOverride(source, &e.name, m) && !found.iter().any(|(_, n)| *n == e.name) {
|
|
58
|
-
found.push((ancestors(source, &e.name).len(), e.name.clone()));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
found.sort_by(|a, b| b.0.cmp(&a.0));
|
|
62
|
-
found.into_iter().map(|(_, n)| n).collect()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/// Every `(type, method)` a call may dispatch dynamically: each ancestor of an
|
|
66
|
-
/// overriding struct paired with the overridden method.
|
|
67
|
-
pub fn virtualMethods(source: &Source) -> HashSet<(String, String)> {
|
|
68
|
-
let mut out = HashSet::new();
|
|
69
|
-
for i in &source.items {
|
|
70
|
-
let Item::Fn(f) = i else { continue };
|
|
71
|
-
if !f.is_override {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
let Some(s) = f.type_param.as_deref() else { continue };
|
|
75
|
-
for a in ancestors(source, s) {
|
|
76
|
-
out.insert((a.to_string(), f.name.clone()));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
out
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/// Every call that may run an override: `(type, method)` -> the `Type.method` key of each
|
|
83
|
-
/// override below that type, deepest first. A call on the type runs its own method or one of these.
|
|
84
|
-
pub fn overrideTargets(source: &Source) -> HashMap<(String, String), Vec<String>> {
|
|
85
|
-
virtualMethods(source)
|
|
86
|
-
.into_iter()
|
|
87
|
-
.map(|(t, m)| {
|
|
88
|
-
let keys = overriders(source, &t, &m).into_iter().map(|s| format!("{s}.{m}")).collect();
|
|
89
|
-
((t, m), keys)
|
|
90
|
-
})
|
|
91
|
-
.collect()
|
|
92
|
-
}
|
compiler/core/src/tests_lower.rs
CHANGED
|
@@ -114,8 +114,6 @@ fn desugarTestsToFns(source: &crate::ast::Source) -> (crate::ast::Source, Vec<(S
|
|
|
114
114
|
is_extern: false,
|
|
115
115
|
is_export: false,
|
|
116
116
|
is_test: true,
|
|
117
|
-
is_override: false,
|
|
118
|
-
is_abstract: false,
|
|
119
117
|
is_fip: false,
|
|
120
118
|
has_self: true,
|
|
121
119
|
params: ioParam.clone(),
|
compiler/core/tests/core/closure_spec_test.rs
CHANGED
|
@@ -419,19 +419,3 @@ fn identical_closures_share_a_copy() {
|
|
|
419
419
|
assert!(!names.contains(&"List.map$c1".to_string()), "{names:?}");
|
|
420
420
|
assert_eq!(runInt("dedup", &src), 18);
|
|
421
421
|
}
|
|
422
|
-
|
|
423
|
-
fn specialised(body: &str) -> Vec<String> {
|
|
424
|
-
let mut s = plum_test_support::parseRawSource(body);
|
|
425
|
-
plum_core::closure_spec::specialiseWith(&mut s, &plum_core::closure_spec::Hints::new())
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const EACH: &str = "struct Actor(w: Int) =\n fun each(self, f: fn(Int) -> Int) -> Int = f(self.w)\n\nfun main(a: Actor) -> Int = a.each(|x| x + 1)\n";
|
|
429
|
-
|
|
430
|
-
#[test]
|
|
431
|
-
fn virtual_method_is_not_specialised() {
|
|
432
|
-
let over = "struct Ship(Actor(w), t: Int) =\n override fun each(self, f: fn(Int) -> Int) -> Int = f(self.t)\n\n";
|
|
433
|
-
let plain = specialised(EACH);
|
|
434
|
-
assert!(!plain.is_empty(), "control: a non-virtual method is specialised");
|
|
435
|
-
let src = EACH.replacen("fun main", &format!("{over}fun main"), 1);
|
|
436
|
-
assert!(specialised(&src).is_empty());
|
|
437
|
-
}
|
compiler/core/tests/core/formatter_test.rs
CHANGED
|
@@ -265,17 +265,9 @@ fn formatAnnotatedDeclaration() {
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
#[test]
|
|
268
|
-
fn
|
|
268
|
+
fn format_struct_header() {
|
|
269
|
-
let out = formatSource("struct House(
|
|
269
|
+
let out = formatSource("struct House(x:Int,k:Int)\n").expect("format");
|
|
270
|
-
assert!(out.contains("struct House(
|
|
270
|
+
assert!(out.contains("struct House(x: Int, k: Int)"), "got: {out}");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
#[test]
|
|
274
|
-
fn format_record_header_selection() {
|
|
275
|
-
let once = formatSource("struct Ship(Actor( x,y ),tex:Int)\n").expect("format");
|
|
276
|
-
assert!(once.contains("struct Ship(Actor(x, y), tex: Int)"), "got: {once}");
|
|
277
|
-
let twice = formatSource(&once).expect("second");
|
|
278
|
-
assert_eq!(once, twice, "not idempotent:\n{once}");
|
|
279
271
|
}
|
|
280
272
|
|
|
281
273
|
#[test]
|
|
@@ -287,15 +279,6 @@ fn format_typed_self_assign() {
|
|
|
287
279
|
assert_eq!(once, twice, "not idempotent:\n{once}");
|
|
288
280
|
}
|
|
289
281
|
|
|
290
|
-
#[test]
|
|
291
|
-
fn format_override_fun() {
|
|
292
|
-
let input = "struct Rr(a: Int) =\n override fun draw(self) = 1\n";
|
|
293
|
-
let once = formatSource(input).expect("format");
|
|
294
|
-
assert!(once.contains(" override fun draw(self) = 1"), "got: {once}");
|
|
295
|
-
let twice = formatSource(&once).expect("second");
|
|
296
|
-
assert_eq!(once, twice, "not idempotent:\n{once}");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
282
|
#[test]
|
|
300
283
|
fn formatMatchBodyOneLine() {
|
|
301
284
|
let input = "fun f(x: Int) -> Int = match x\n 0 => 1\n _ => 2\n";
|
compiler/core/tests/core/layout_test.rs
CHANGED
|
@@ -37,12 +37,6 @@ fn recursive_is_boxed() {
|
|
|
37
37
|
assert_eq!(layoutOf("struct Loop(next: Loop, n: Int)\n", "Loop"), Layout::Boxed);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
#[test]
|
|
41
|
-
fn embedding_is_boxed() {
|
|
42
|
-
let src = "struct Actor(x: Float)\nstruct Walker(Actor, speed: Float)\n";
|
|
43
|
-
assert_eq!(layoutOf(src, "Walker"), Layout::Boxed);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
#[test]
|
|
47
41
|
fn mut_field_is_boxed() {
|
|
48
42
|
assert_eq!(layoutOf("struct Counter(mut n: Int)\n", "Counter"), Layout::Boxed);
|
|
@@ -105,17 +99,3 @@ fn std_and_examples_layouts() {
|
|
|
105
99
|
}
|
|
106
100
|
}
|
|
107
101
|
|
|
108
|
-
#[test]
|
|
109
|
-
fn baseIsBoxed() {
|
|
110
|
-
let src = "struct Actor(x: Int)\nstruct Ship(Actor(x), s: Int)\nstruct Plain(x: Int)\n";
|
|
111
|
-
assert_eq!(layoutOf(src, "Actor"), Layout::Boxed);
|
|
112
|
-
assert_eq!(layoutOf(src, "Ship"), Layout::Boxed);
|
|
113
|
-
assert_eq!(layoutOf(src, "Plain"), Layout::Flat);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
#[test]
|
|
117
|
-
fn secondEmbedStaysFlat() {
|
|
118
|
-
let src = "struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Cc(Aa(x), Bb(y))\n";
|
|
119
|
-
assert_eq!(layoutOf(src, "Bb"), Layout::Flat);
|
|
120
|
-
assert_eq!(layoutOf(src, "Aa"), Layout::Boxed);
|
|
121
|
-
}
|
compiler/core/tests/core/loader_test.rs
CHANGED
|
@@ -545,7 +545,7 @@ fn single_file_equals_loader() {
|
|
|
545
545
|
let programs = [
|
|
546
546
|
"fun id(a: Unit) -> Unit = a\nfun main(u: Unit) = id(u)\n",
|
|
547
547
|
"struct Pt(x: Unit, y: Unit)\nfun mk(u: Unit) = Pt(x: u, y: u)\n",
|
|
548
|
-
"struct
|
|
548
|
+
"struct Boss(x: Unit, hp: Unit)\nfun main(u: Unit) = Boss(x: u, hp: u)\n",
|
|
549
549
|
"enum Shape = Circle | Dot\nfun f(s: Shape) = s\n",
|
|
550
550
|
"enum Shape = Circle(r: Unit) | Sq\nfun f(s: Shape) = s\n",
|
|
551
551
|
];
|
compiler/core/tests/core/lower_test.rs
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
#![allow(non_snake_case)]
|
|
2
|
-
use plum_core::lower::frontEndDiag;
|
|
3
|
-
|
|
4
|
-
#[test]
|
|
5
|
-
fn lowersEmbeds() {
|
|
6
|
-
let mut s = plum_test_support::parseRawSource(
|
|
7
|
-
"struct Actor(x: Int) =\n fun up(self) -> Int = 0\n\nstruct Hut(Actor(x), k: Int) =\n override fun up(self) -> Int = 1\n",
|
|
8
|
-
);
|
|
9
|
-
assert_eq!(frontEndDiag(&mut s), Ok(()));
|
|
10
|
-
}
|
|
11
2
|
|
|
12
3
|
#[test]
|
|
13
4
|
fn trait_declaration_is_a_parse_error() {
|
|
@@ -17,11 +8,3 @@ fn trait_declaration_is_a_parse_error() {
|
|
|
17
8
|
assert!(parser.parse(src, None).unwrap().root_node().has_error());
|
|
18
9
|
}
|
|
19
10
|
|
|
20
|
-
#[test]
|
|
21
|
-
fn second_embed_errors() {
|
|
22
|
-
let mut s = plum_test_support::parseRawSource(
|
|
23
|
-
"struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Ss(Aa(x), Bb(y))\n",
|
|
24
|
-
);
|
|
25
|
-
let e = frontEndDiag(&mut s).unwrap_err();
|
|
26
|
-
assert!(e.iter().any(|d| d.message.contains("struct Ss embeds Aa and Bb; a struct can embed only one struct")), "{e:?}");
|
|
27
|
-
}
|
compiler/core/tests/core/main.rs
CHANGED
|
@@ -13,17 +13,14 @@ mod list_literal_test;
|
|
|
13
13
|
mod loader_test;
|
|
14
14
|
mod lower_test;
|
|
15
15
|
mod match_body_parse_test;
|
|
16
|
-
mod override_parse_test;
|
|
17
16
|
mod parser_test;
|
|
18
17
|
mod purity_test;
|
|
19
18
|
mod question_parse_test;
|
|
20
|
-
mod record_embed_test;
|
|
21
19
|
mod record_init_parse_test;
|
|
22
20
|
mod record_init_test;
|
|
23
21
|
mod rename_test;
|
|
24
22
|
mod session_test;
|
|
25
23
|
mod simplify_test;
|
|
26
|
-
mod subtyping_test;
|
|
27
24
|
mod tail_test;
|
|
28
25
|
mod tests_lower_test;
|
|
29
26
|
mod trmc_test;
|
compiler/core/tests/core/override_parse_test.rs
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#![allow(non_snake_case)]
|
|
2
|
-
use plum_core::ast::*;
|
|
3
|
-
|
|
4
|
-
fn parseFns(src: &str) -> Vec<Fn> {
|
|
5
|
-
let source = plum_test_support::parseRawSource(src);
|
|
6
|
-
source
|
|
7
|
-
.items
|
|
8
|
-
.iter()
|
|
9
|
-
.filter_map(|i| match i {
|
|
10
|
-
Item::Fn(f) => Some(f.clone()),
|
|
11
|
-
_ => None,
|
|
12
|
-
})
|
|
13
|
-
.collect()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#[test]
|
|
17
|
-
fn overrideFunParses() {
|
|
18
|
-
let fns = parseFns("struct Rr(a: Int) =\n override fun draw(self) = 1\n");
|
|
19
|
-
let f = fns.iter().find(|f| f.name == "draw").expect("draw");
|
|
20
|
-
assert_eq!(f.type_param.as_deref(), Some("Rr"));
|
|
21
|
-
assert!(f.is_override);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
#[test]
|
|
25
|
-
fn plainFunIsNotOverride() {
|
|
26
|
-
let fns = parseFns("struct Rr(a: Int) =\n fun draw(self) = 1\n");
|
|
27
|
-
let f = fns.iter().find(|f| f.name == "draw").expect("draw");
|
|
28
|
-
assert!(!f.is_override);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#[test]
|
|
32
|
-
fn attributeOnAttributeParses() {
|
|
33
|
-
let fns = parseFns("fun main() =\n self.Stage.draw()\n");
|
|
34
|
-
assert_eq!(fns.len(), 1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#[test]
|
|
38
|
-
fn overrideStaysAnIdentifier() {
|
|
39
|
-
let fns = parseFns("fun main() =\n overrides := 1\n overrider := overrides\n override := 2\n");
|
|
40
|
-
assert_eq!(fns.len(), 1);
|
|
41
|
-
assert!(!fns[0].is_override);
|
|
42
|
-
}
|
compiler/core/tests/core/parser_test.rs
CHANGED
|
@@ -813,25 +813,13 @@ fn groupingKeepsPrecedenceWithoutNode() {
|
|
|
813
813
|
assert!(matches!(b.right, Expr { kind: ExprKind::Int(3), .. }));
|
|
814
814
|
}
|
|
815
815
|
|
|
816
|
-
#[test]
|
|
817
|
-
fn abstractFunSetsFlag() {
|
|
818
|
-
let s = parseRaw("struct Aa(a: Int) =\n abstract fun draw(self) -> Int\n fun other(self) -> Int = 1\n");
|
|
819
|
-
let fns: Vec<&Fn> = s
|
|
820
|
-
.items
|
|
821
|
-
.iter()
|
|
822
|
-
.filter_map(|i| if let Item::Fn(f) = i { Some(f) } else { None })
|
|
823
|
-
.collect();
|
|
824
|
-
assert!(fns.iter().any(|f| f.name == "draw" && f.is_abstract && matches!(f.body, FnBody::Extern)));
|
|
825
|
-
assert!(fns.iter().any(|f| f.name == "other" && !f.is_abstract));
|
|
826
|
-
}
|
|
827
|
-
|
|
828
816
|
#[test]
|
|
829
817
|
fn fipFunSetsFlag() {
|
|
830
|
-
let s = parseRaw("fip fun a1() -> Int = 1\nfun b1() -> Int = 2\nstruct Sq(x: Int) =\n
|
|
818
|
+
let s = parseRaw("fip fun a1() -> Int = 1\nfun b1() -> Int = 2\nstruct Sq(x: Int) =\n fip fun d(self) -> Int = self.x\n");
|
|
831
819
|
let fns: Vec<&Fn> = s.items.iter().filter_map(|i| if let Item::Fn(f) = i { Some(f) } else { None }).collect();
|
|
832
|
-
assert!(fns.iter().any(|f| f.name == "a1" && f.is_fip
|
|
820
|
+
assert!(fns.iter().any(|f| f.name == "a1" && f.is_fip));
|
|
833
821
|
assert!(fns.iter().any(|f| f.name == "b1" && !f.is_fip));
|
|
834
|
-
assert!(fns.iter().any(|f| f.name == "d" && f.is_fip
|
|
822
|
+
assert!(fns.iter().any(|f| f.name == "d" && f.is_fip));
|
|
835
823
|
}
|
|
836
824
|
|
|
837
825
|
#[test]
|
compiler/core/tests/core/purity_test.rs
CHANGED
|
@@ -101,17 +101,6 @@ fn closure_capturing_io_impure() {
|
|
|
101
101
|
assert_eq!(pure(&s), set(&["c"]));
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
#[test]
|
|
105
|
-
fn virtual_call_through_base_is_impure() {
|
|
106
|
-
let s = format!(
|
|
107
|
-
"{P}struct Actor(w: Int) =\n fun go(self, io: Io) -> Int = 1\n\nstruct Ship(Actor(w), t: Int) =\n override fun go(self, io: Io) -> Int =\n io.printLn(\"x\")\n 2\n\nfun run(a: Actor, io: Io) -> Int = a.go(io)\nfun leaf(s: Ship, io: Io) -> Int = s.go(io)\nfun plain(a: Actor) -> Int = a.w\n"
|
|
108
|
-
);
|
|
109
|
-
let p = pure(&s);
|
|
110
|
-
assert!(!p.contains("Actor.go"), "{p:?}");
|
|
111
|
-
assert!(!p.contains("run"), "{p:?}");
|
|
112
|
-
assert!(p.contains("plain"), "{p:?}");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
104
|
#[test]
|
|
116
105
|
fn nonvirtual_method_stays_pure() {
|
|
117
106
|
let s = format!("{P}struct Actor(w: Int) =\n fun go(self) -> Int = 1\n\nfun run(a: Actor) -> Int = a.go()\n");
|
compiler/core/tests/core/record_embed_test.rs
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#![allow(non_snake_case)]
|
|
2
|
-
use plum_core::ast::*;
|
|
3
|
-
use plum_core::ctors::lowerCtors;
|
|
4
|
-
use plum_core::embeds::lowerEmbeds;
|
|
5
|
-
|
|
6
|
-
fn parseRaw(src: &str) -> Source {
|
|
7
|
-
plum_test_support::parseRawSource(src)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
fn lowerAll(src: &str) -> (Source, Vec<String>) {
|
|
11
|
-
let mut s = parseRaw(src);
|
|
12
|
-
let mut errs = lowerEmbeds(&mut s);
|
|
13
|
-
errs.extend(lowerCtors(&mut s));
|
|
14
|
-
(s, errs)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fn enumOf<'a>(s: &'a Source, name: &str) -> &'a Enum {
|
|
18
|
-
s.items
|
|
19
|
-
.iter()
|
|
20
|
-
.find_map(|i| match i {
|
|
21
|
-
Item::Enum(e) if e.name == name => Some(e),
|
|
22
|
-
_ => None,
|
|
23
|
-
})
|
|
24
|
-
.unwrap()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
fn fnOf<'a>(s: &'a Source, rec: &str, name: &str) -> Option<&'a Fn> {
|
|
28
|
-
s.items.iter().find_map(|i| match i {
|
|
29
|
-
Item::Fn(f) if f.type_param.as_deref() == Some(rec) && f.name == name => Some(f),
|
|
30
|
-
_ => None,
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const STAGE: &str = "struct Stage(camScale: Float) =
|
|
35
|
-
fun init(self) =
|
|
36
|
-
self.root: Int = 0
|
|
37
|
-
self.camX = 0.0
|
|
38
|
-
|
|
39
|
-
fun act(self, dt: Float) -> Int = 1
|
|
40
|
-
|
|
41
|
-
fun draw(self) -> Int = 2
|
|
42
|
-
|
|
43
|
-
fun create() -> Int = 3
|
|
44
|
-
|
|
45
|
-
";
|
|
46
|
-
|
|
47
|
-
fn world(extra: &str) -> String {
|
|
48
|
-
format!(
|
|
49
|
-
"{STAGE}struct World(Stage(camScale), score: Int) =\n fun init(self) =\n self.best = 0\n\n{extra}"
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#[test]
|
|
54
|
-
fn fieldOrderAndCopiedMethods() {
|
|
55
|
-
let (s, errs) = lowerAll(&world(" override fun draw(self) -> Int = self.Stage.draw()\n"));
|
|
56
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
57
|
-
let w = enumOf(&s, "World");
|
|
58
|
-
assert_eq!(
|
|
59
|
-
w.variants[0].field_names,
|
|
60
|
-
vec!["camScale", "root", "camX", "score", "best"]
|
|
61
|
-
);
|
|
62
|
-
assert!(fnOf(&s, "World", "act").is_some());
|
|
63
|
-
assert!(fnOf(&s, "World", "create").is_none(), "statics are not inherited");
|
|
64
|
-
assert!(fnOf(&s, "World", "init").is_none());
|
|
65
|
-
assert!(fnOf(&s, "World", "Stage__draw").is_some());
|
|
66
|
-
let draw = fnOf(&s, "World", "draw").unwrap();
|
|
67
|
-
assert!(draw.is_override);
|
|
68
|
-
let FnBody::Expr(Expr { kind: ExprKind::Attribute(a), .. }) = &draw.body else { panic!("{:?}", draw.body) };
|
|
69
|
-
let AttrKind::Method(c) = &a.attr else { panic!() };
|
|
70
|
-
assert_eq!(c.name, "Stage__draw");
|
|
71
|
-
assert!(matches!(a.object.kind, ExprKind::Self_));
|
|
72
|
-
assert!(fnOf(&s, "Stage", "draw").is_some());
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#[test]
|
|
76
|
-
fn notReplacedKeepsPlainName() {
|
|
77
|
-
let (s, errs) = lowerAll(&world(" fun run(self) -> Int = self.Stage.draw()\n"));
|
|
78
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
79
|
-
assert!(fnOf(&s, "World", "Stage__draw").is_none());
|
|
80
|
-
assert!(fnOf(&s, "World", "draw").is_some());
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#[test]
|
|
84
|
-
fn embeddedRecordStillStandalone() {
|
|
85
|
-
let (s, errs) = lowerAll(&world(""));
|
|
86
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
87
|
-
assert_eq!(
|
|
88
|
-
enumOf(&s, "Stage").variants[0].field_names,
|
|
89
|
-
vec!["camScale", "root", "camX"]
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#[test]
|
|
94
|
-
fn hidesWithoutOverride() {
|
|
95
|
-
let (_, errs) = lowerAll(&world(" fun draw(self) -> Int = 5\n"));
|
|
96
|
-
assert_eq!(errs, vec!["World.draw hides Stage.draw; mark it override"]);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
#[test]
|
|
100
|
-
fn methodNeedsOverride() {
|
|
101
|
-
let src = "struct Actor(x: Int) =\n fun act(self, dt: Float) = 0\n\nstruct Ship(Actor(x), tex: Int) =\n fun act(self, dt: Float) = 1\n";
|
|
102
|
-
let (_, errs) = lowerAll(src);
|
|
103
|
-
assert_eq!(errs, vec!["Ship.act hides Actor.act; mark it override"]);
|
|
104
|
-
let ok = src.replace(" fun act(self, dt: Float) = 1", " override fun act(self, dt: Float) = 1");
|
|
105
|
-
let (_, errs) = lowerAll(&ok);
|
|
106
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
#[test]
|
|
110
|
-
fn overrideNothing() {
|
|
111
|
-
let (_, errs) = lowerAll(&world(" override fun zap(self) = 1\n"));
|
|
112
|
-
assert_eq!(errs, vec!["World.zap overrides nothing"]);
|
|
113
|
-
let (_, errs) = lowerAll("struct Plain(a: Int) =\n override fun zap(self) = 1\n");
|
|
114
|
-
assert_eq!(errs, vec!["Plain.zap overrides nothing"]);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#[test]
|
|
118
|
-
fn overrideOnFreeFnAndStatic() {
|
|
119
|
-
let (_, errs) = lowerAll("override fun zap() = 1\n");
|
|
120
|
-
assert_eq!(errs.len(), 1, "{errs:?}");
|
|
121
|
-
let (_, errs) = lowerAll("struct Plain(a: Int) =\n override fun make() = 1\n");
|
|
122
|
-
assert_eq!(errs.len(), 1, "{errs:?}");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
#[test]
|
|
126
|
-
fn baseMethodReplacement() {
|
|
127
|
-
let src = "struct Actor(x: Int) =\n fun go(self) -> Int = 1\n\nstruct Ship(Actor(x), tex: Int) =\n override fun go(self) -> Int = self.Actor.go()\n";
|
|
128
|
-
let (s, errs) = lowerAll(src);
|
|
129
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
130
|
-
assert!(fnOf(&s, "Ship", "Actor__go").is_some());
|
|
131
|
-
let (_, errs) = lowerAll(&src.replace("override fun go", "fun go"));
|
|
132
|
-
assert_eq!(errs, vec!["Ship.go hides Actor.go; mark it override"]);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
#[test]
|
|
136
|
-
fn deepBaseThroughEmbeddedStruct() {
|
|
137
|
-
let src = "struct Actor(x: Int) =\n fun go(self) -> Int = 1\n\nstruct Mid(Actor(x), m: Int) =\n override fun go(self) -> Int = 2\n\nstruct Top(Mid(x, m), t: Int) =\n fun init(self) =\n self.u = 1\n";
|
|
138
|
-
let (s, errs) = lowerAll(src);
|
|
139
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
140
|
-
let top = enumOf(&s, "Top");
|
|
141
|
-
assert_eq!(top.variants[0].field_names, vec!["x", "m", "t", "u"]);
|
|
142
|
-
assert_eq!(enumOf(&s, "Top").base(), Some("Mid"));
|
|
143
|
-
assert_eq!(enumOf(&s, "Mid").base(), Some("Actor"));
|
|
144
|
-
assert!(fnOf(&s, "Top", "go").is_some());
|
|
145
|
-
assert!(fnOf(&s, "Top", "Actor__go").is_some());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#[test]
|
|
149
|
-
fn recordChainOrder() {
|
|
150
|
-
let src = "struct Aa(a: Int) =\n fun act(self) = 1\n\nstruct Bb(Aa(a), b: Int) =\n fun bact(self) = 1\n\nstruct Cc(Bb(a, b), c: Int) =\n fun init(self) =\n self.d = 4\n";
|
|
151
|
-
let (s, errs) = lowerAll(src);
|
|
152
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
153
|
-
assert_eq!(enumOf(&s, "Cc").variants[0].field_names, vec!["a", "b", "c", "d"]);
|
|
154
|
-
assert!(fnOf(&s, "Cc", "act").is_some() && fnOf(&s, "Cc", "bact").is_some());
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
#[test]
|
|
158
|
-
fn unselectedHeaderFieldNeverSet() {
|
|
159
|
-
let src = "struct Aa(a: Int, b: Int) =\n fun act(self) = 1\n\nstruct Cc(Aa(a), c: Int) =\n fun init(self) =\n self.d = 1\n";
|
|
160
|
-
let (_, errs) = lowerAll(src);
|
|
161
|
-
assert!(
|
|
162
|
-
errs.iter().any(|e| e == "Cc field b is never set: pass it or set it in init"),
|
|
163
|
-
"{errs:?}"
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
#[test]
|
|
168
|
-
fn secondEmbedIsAnError() {
|
|
169
|
-
let src = "struct Aa(a: Int) =\n fun f(self) = 1\n\nstruct Bb(b: Int) =\n fun f(self) = 2\n\nstruct Cc(Aa(a), Bb(b), c: Int)\n";
|
|
170
|
-
let (_, errs) = lowerAll(src);
|
|
171
|
-
assert!(
|
|
172
|
-
errs.contains(&"struct Cc embeds Aa and Bb; a struct can embed only one struct \u{2014} use a field (bb: Bb)".to_string()),
|
|
173
|
-
"{errs:?}"
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
#[test]
|
|
178
|
-
fn embeddingANonStructIsAnError() {
|
|
179
|
-
let (_, errs) = lowerAll("enum Color = Red | Blue\n\nstruct Cc(Color, c: Int)\n");
|
|
180
|
-
assert!(errs.contains(&"type Cc cannot embed Color: it is not a struct".to_string()), "{errs:?}");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
#[test]
|
|
184
|
-
fn selfXNotEmbedded() {
|
|
185
|
-
let (_, errs) = lowerAll(&world(" fun run(self) -> Int = self.Nope.draw()\n"));
|
|
186
|
-
assert_eq!(errs, vec!["World.run calls self.Nope.draw, but World does not embed Nope"]);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
#[test]
|
|
190
|
-
fn genericRecordCannotBeEmbedded() {
|
|
191
|
-
let src = "struct Box[T](v: T) =\n fun get(self) -> T = self.v\n\nstruct Cc(Box(v), c: Int)\n";
|
|
192
|
-
let (_, errs) = lowerAll(src);
|
|
193
|
-
assert!(errs.contains(&"cannot embed generic struct Box".to_string()), "{errs:?}");
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
#[test]
|
|
197
|
-
fn idempotent() {
|
|
198
|
-
let mut s = parseRaw(&world(" override fun draw(self) -> Int = self.Stage.draw()\n"));
|
|
199
|
-
assert!(lowerEmbeds(&mut s).is_empty());
|
|
200
|
-
let once = s.clone();
|
|
201
|
-
assert!(lowerEmbeds(&mut s).is_empty());
|
|
202
|
-
assert_eq!(once.items, s.items);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
#[test]
|
|
206
|
-
fn staticsNotCopied() {
|
|
207
|
-
let (s, errs) = lowerAll(&world(""));
|
|
208
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
209
|
-
assert!(fnOf(&s, "World", "create").is_none());
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const ABSTRACT: &str = "struct Actor(x: Int) =\n abstract fun draw(self) -> Int\n\n";
|
|
213
|
-
|
|
214
|
-
#[test]
|
|
215
|
-
fn abstractMethodNeedsOverride() {
|
|
216
|
-
let src = format!("{ABSTRACT}struct Ship(Actor(x), tex: Int) =\n fun zap(self) = 1\n");
|
|
217
|
-
let (_, errs) = lowerAll(&src);
|
|
218
|
-
assert_eq!(errs, vec!["struct Ship embeds Actor but does not override abstract method draw"]);
|
|
219
|
-
let ok = format!("{ABSTRACT}struct Ship(Actor(x), tex: Int) =\n override fun draw(self) -> Int = 1\n");
|
|
220
|
-
let (s, errs) = lowerAll(&ok);
|
|
221
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
222
|
-
assert!(fnOf(&s, "Ship", "Actor__draw").is_none());
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
#[test]
|
|
226
|
-
fn abstractStructCannotBeConstructed() {
|
|
227
|
-
let src = format!("{ABSTRACT}fun make() -> Actor = Actor(x: 1)\n");
|
|
228
|
-
let (_, errs) = lowerAll(&src);
|
|
229
|
-
assert_eq!(errs, vec!["cannot construct Actor: draw has no body"]);
|
|
230
|
-
}
|
compiler/core/tests/core/record_init_parse_test.rs
CHANGED
|
@@ -25,21 +25,26 @@ fn enumOf<'a>(s: &'a Source, name: &str) -> &'a Enum {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
#[test]
|
|
28
|
-
fn
|
|
28
|
+
fn embedded_struct_fields_are_a_syntax_error() {
|
|
29
|
-
|
|
29
|
+
assert!(tryParse("struct Ship(Actor(x, y), tex: Int)\n").is_none());
|
|
30
|
-
let e = enumOf(&s, "Ship");
|
|
31
|
-
assert_eq!(
|
|
32
|
-
e.structInfo().unwrap().embedFields,
|
|
33
|
-
vec![("Actor".to_string(), vec!["x".to_string(), "y".to_string()])]
|
|
34
|
-
);
|
|
35
|
-
|
|
30
|
+
assert!(tryParse("struct Ship(Actor, tex: Int)\n").is_none());
|
|
36
|
-
|
|
31
|
+
assert!(tryParse("struct A(B(x))\n").is_none());
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
#[test]
|
|
35
|
+
fn override_is_a_syntax_error() {
|
|
36
|
+
assert!(tryParse("struct Rr(a: Int) =\n override fun draw(self) = 1\n").is_none());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[test]
|
|
40
|
+
fn abstract_is_a_syntax_error() {
|
|
41
|
+
assert!(tryParse("struct Aa(a: Int) =\n abstract fun draw(self) -> Int\n").is_none());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[test]
|
|
40
|
-
fn
|
|
45
|
+
fn a_struct_header_keeps_only_its_own_fields() {
|
|
41
|
-
let s = tryParse("struct Ship(
|
|
46
|
+
let s = tryParse("struct Ship(x: Int, tex: Int)\n").unwrap();
|
|
42
|
-
assert_eq!(enumOf(&s, "Ship").
|
|
47
|
+
assert_eq!(enumOf(&s, "Ship").variants[0].field_names, vec!["x", "tex"]);
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
#[test]
|
compiler/core/tests/core/record_init_test.rs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#![allow(non_snake_case)]
|
|
2
2
|
use plum_core::ast::*;
|
|
3
3
|
use plum_core::ctors::lowerCtors;
|
|
4
|
-
use plum_core::embeds::lowerEmbeds;
|
|
5
4
|
|
|
6
5
|
fn parseRaw(src: &str) -> Source {
|
|
7
6
|
plum_test_support::parseRawSource(src)
|
|
@@ -9,8 +8,6 @@ fn parseRaw(src: &str) -> Source {
|
|
|
9
8
|
|
|
10
9
|
fn lower(src: &str) -> (Source, Vec<String>) {
|
|
11
10
|
let mut s = parseRaw(src);
|
|
12
|
-
let te = lowerEmbeds(&mut s);
|
|
13
|
-
assert!(te.is_empty(), "{te:?}");
|
|
14
11
|
let e = lowerCtors(&mut s);
|
|
15
12
|
(s, e)
|
|
16
13
|
}
|
|
@@ -32,19 +29,14 @@ fn fnNamed<'a>(s: &'a Source, name: &str) -> Option<&'a Fn> {
|
|
|
32
29
|
})
|
|
33
30
|
}
|
|
34
31
|
|
|
35
|
-
const ACTOR: &str = "struct Actor(x: Float, y: Float) =
|
|
36
|
-
fun init(self) =
|
|
37
|
-
self.width: Float = 1.0
|
|
38
|
-
self.height: Float = 2.0
|
|
39
|
-
|
|
40
|
-
";
|
|
41
|
-
|
|
42
32
|
fn ship(extra: &str) -> String {
|
|
43
33
|
format!(
|
|
44
|
-
"
|
|
34
|
+
"struct Ship(x: Float, y: Float, tex: Int) =
|
|
45
35
|
fun init(self) =
|
|
36
|
+
self.width: Float = 1.0
|
|
37
|
+
self.height: Float = 2.0
|
|
46
38
|
self.height = self.width
|
|
47
|
-
self.vx = 0.0
|
|
39
|
+
self.vx: Float = 0.0
|
|
48
40
|
{extra}
|
|
49
41
|
fun main() =
|
|
50
42
|
s := Ship(x: 1.0, y: 2.0, tex: 3)
|
|
@@ -57,9 +49,8 @@ fn ship_example_lowers() {
|
|
|
57
49
|
let (s, e) = lower(&ship(""));
|
|
58
50
|
assert!(e.is_empty(), "{e:?}");
|
|
59
51
|
let en = enumOf(&s, "Ship");
|
|
60
|
-
assert_eq!(en.variants[0].field_names, vec!["x", "y", "
|
|
52
|
+
assert_eq!(en.variants[0].field_names, vec!["x", "y", "tex", "width", "height", "vx"]);
|
|
61
53
|
assert_eq!(en.variants[0].fields.last().unwrap().name, "Float");
|
|
62
|
-
assert!(en.structInfo().unwrap().embedFields.is_empty());
|
|
63
54
|
let n = fnNamed(&s, "Ship__new").unwrap();
|
|
64
55
|
let names: Vec<_> = n.params.iter().map(|p| p.name.as_str()).collect();
|
|
65
56
|
assert_eq!(names, vec!["__p_x", "__p_y", "__p_tex"]);
|
|
@@ -76,12 +67,6 @@ fn unknown_constructor_parameter() {
|
|
|
76
67
|
assert!(e.contains(&"Ship has no constructor parameter width".to_string()), "{e:?}");
|
|
77
68
|
}
|
|
78
69
|
|
|
79
|
-
#[test]
|
|
80
|
-
fn never_set_field() {
|
|
81
|
-
let (_, e) = lower("struct Aa(x: Int, color: Int)\n\nstruct Ship(Aa(x), tex: Int)\n");
|
|
82
|
-
assert_eq!(e, vec!["Ship field color is never set: pass it or set it in init"]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
70
|
#[test]
|
|
86
71
|
fn method_call_on_self_before_all_set() {
|
|
87
72
|
let src = "struct Rec(a: Int) =
|
|
@@ -145,26 +130,6 @@ fn idempotent() {
|
|
|
145
130
|
assert_eq!(s, before);
|
|
146
131
|
}
|
|
147
132
|
|
|
148
|
-
#[test]
|
|
149
|
-
fn embedded_inits_run_down_the_chain_before_the_struct_init() {
|
|
150
|
-
let src = "struct Aa(k0: Int) =
|
|
151
|
-
fun init(self) =
|
|
152
|
-
self.p: Int = 1
|
|
153
|
-
|
|
154
|
-
struct Bb(Aa(k0)) =
|
|
155
|
-
fun init(self) =
|
|
156
|
-
self.q: Int = self.p + 1
|
|
157
|
-
|
|
158
|
-
struct Rec(Bb(k0), k: Int) =
|
|
159
|
-
fun init(self) =
|
|
160
|
-
self.r: Int = self.q + 1
|
|
161
|
-
";
|
|
162
|
-
let (s, e) = lower(src);
|
|
163
|
-
assert!(e.is_empty(), "{e:?}");
|
|
164
|
-
assert_eq!(enumOf(&s, "Rec").variants[0].field_names, vec!["k0", "p", "q", "k", "r"]);
|
|
165
|
-
assert!(s.items.iter().all(|i| !matches!(i, Item::Fn(f) if f.name == "init")));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
133
|
#[test]
|
|
169
134
|
fn nested_construction_rewritten() {
|
|
170
135
|
let src = format!(
|
|
@@ -203,11 +168,3 @@ fn named_args_map_by_name_in_any_order() {
|
|
|
203
168
|
assert!(matches!(&c.args[0], Arg::Positional(Expr { kind: ExprKind::Int(2), .. })));
|
|
204
169
|
assert!(matches!(&c.args[1], Arg::Positional(Expr { kind: ExprKind::Float(_), .. })));
|
|
205
170
|
}
|
|
206
|
-
|
|
207
|
-
#[test]
|
|
208
|
-
fn embeddedInitRunsBetweenAndExposedFieldsWork() {
|
|
209
|
-
let src = "struct Actor(width: Int, height: Int) =\n fun init(self) =\n self.area: Int = self.width * self.height\n\nstruct Ship(Actor(width, height), tex: Int) =\n fun init(self) =\n self.id = 7\n\nfun make() -> Ship = Ship(width: 1, height: 2, tex: 3)\n";
|
|
210
|
-
let (s, errs) = lower(src);
|
|
211
|
-
assert!(errs.is_empty(), "{errs:?}");
|
|
212
|
-
assert!(fnNamed(&s, "Ship__new").is_some());
|
|
213
|
-
}
|
compiler/core/tests/core/subtyping_test.rs
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#![allow(non_snake_case)]
|
|
2
|
-
use plum_core::subtyping::*;
|
|
3
|
-
|
|
4
|
-
const SRC: &str = "struct Actor(x: Int) =\n fun draw(self) -> Int = 1\nstruct Ship(Actor(x), s: Int) =\n override fun draw(self) -> Int = 2\nstruct Boss(Ship(x, s), b: Int)\n";
|
|
5
|
-
|
|
6
|
-
#[test]
|
|
7
|
-
fn subtypeChain() {
|
|
8
|
-
let s = plum_test_support::parseRawSource(SRC);
|
|
9
|
-
assert!(isSubtype(&s, "Ship", "Actor"));
|
|
10
|
-
assert!(isSubtype(&s, "Boss", "Actor"));
|
|
11
|
-
assert!(isSubtype(&s, "Boss", "Ship"));
|
|
12
|
-
assert!(isSubtype(&s, "Ship", "Ship"));
|
|
13
|
-
assert!(!isSubtype(&s, "Actor", "Ship"));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#[test]
|
|
17
|
-
fn overridersCountOwnOverrides() {
|
|
18
|
-
let s = plum_test_support::parseRawSource(SRC);
|
|
19
|
-
assert_eq!(overriders(&s, "Actor", "draw"), vec!["Ship"]);
|
|
20
|
-
let v = virtualMethods(&s);
|
|
21
|
-
assert_eq!(v.len(), 1);
|
|
22
|
-
assert!(v.contains(&("Actor".to_string(), "draw".to_string())));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
#[test]
|
|
26
|
-
fn deepestFirst() {
|
|
27
|
-
let src = format!("{}struct Deep(Boss(x, s, b)) =\n override fun draw(self) -> Int = 3\n", SRC);
|
|
28
|
-
let s = plum_test_support::parseRawSource(&src);
|
|
29
|
-
assert_eq!(overriders(&s, "Actor", "draw"), vec!["Deep", "Ship"]);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#[test]
|
|
33
|
-
fn secondEmbedIsNotABase() {
|
|
34
|
-
let s = plum_test_support::parseRawSource("struct Aa(x: Int)\nstruct Bb(y: Int)\nstruct Cc(Aa(x), Bb(y))\n");
|
|
35
|
-
assert!(isSubtype(&s, "Cc", "Aa"));
|
|
36
|
-
assert!(!isSubtype(&s, "Cc", "Bb"));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
#[test]
|
|
40
|
-
fn genericBaseIsNoBase() {
|
|
41
|
-
let s = plum_test_support::parseRawSource("struct Gen[T](x: T)\nstruct Hh(Gen(x))\n");
|
|
42
|
-
assert!(!isSubtype(&s, "Hh", "Gen"));
|
|
43
|
-
}
|
compiler/core/tests/core/uniqueness_test.rs
CHANGED
|
@@ -341,23 +341,11 @@ fn mixed_unique_and_shared_calls_say_so() {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
#[test]
|
|
344
|
-
fn
|
|
344
|
+
fn a_method_storing_self_is_not_proved_in_place() {
|
|
345
|
-
let s = "struct Base(label: Str, n: Int) =\n fun step(self) -> Base = Base(..self, n: self.n + 1)\n\
|
|
346
|
-
|
|
345
|
+
let s = "struct Stored(label: Str, n: Int, log: Slice[Stored]) =\n fun step(self) -> Stored =\n self.log.push(self)\n Stored(..self, n: self.n + 1)\n\
|
|
347
|
-
fun go(b:
|
|
346
|
+
fun go(b: Stored) -> Int =\n c := b.step()\n b.n + c.n\n";
|
|
348
347
|
let (_, r) = run(s);
|
|
349
|
-
assert!(!provedFn(&r, "
|
|
348
|
+
assert!(!provedFn(&r, "Stored.step"), "{r:?}");
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
#[test]
|
|
353
|
-
fn override_storing_self_with_a_unique_receiver_stays_a_copy_of_the_stored_value() {
|
|
354
|
-
// The receiver is dead after the call, so Base.step may write in place; Sub.step keeps
|
|
355
|
-
// `self` in its log, and the log is the only holder left.
|
|
356
|
-
let s = "struct Base(label: Str, n: Int) =\n fun step(self) -> Base = Base(..self, n: self.n + 1)\n\
|
|
357
|
-
struct Sub(Base(label, n), log: Slice[Base]) =\n override fun step(self) -> Base =\n self.log.push(self)\n Base(label: self.label, n: self.n + 1)\n\
|
|
358
|
-
fun go() -> Base =\n b := Base(label: \"a\", n: 0)\n b.step()\n";
|
|
359
|
-
let (_, r) = run(s);
|
|
360
|
-
assert!(!provedFn(&r, "Sub.step"), "{r:?}");
|
|
361
349
|
}
|
|
362
350
|
|
|
363
351
|
#[test]
|
compiler/lsp/src/completion.rs
CHANGED
|
@@ -26,7 +26,6 @@ use tower_lsp::lsp_types::*;
|
|
|
26
26
|
|
|
27
27
|
const KEYWORDS: &[&str] = &[
|
|
28
28
|
"fun",
|
|
29
|
-
"override",
|
|
30
29
|
"fn",
|
|
31
30
|
"crash",
|
|
32
31
|
"struct",
|
|
@@ -36,7 +35,6 @@ const KEYWORDS: &[&str] = &[
|
|
|
36
35
|
"module",
|
|
37
36
|
"extern",
|
|
38
37
|
"export",
|
|
39
|
-
"abstract",
|
|
40
38
|
"fip",
|
|
41
39
|
"if",
|
|
42
40
|
"then",
|
|
@@ -162,8 +160,7 @@ pub fn staticItems(source: &ast::Source, owner: &str) -> Vec<CompletionItem> {
|
|
|
162
160
|
items
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
/// Constructor parameters (name, type text) of struct `owner`:
|
|
163
|
+
/// Constructor parameters (name, type text) of struct `owner`: its own fields.
|
|
166
|
-
/// embedded-struct fields its header selects (`Actor(x, y)`) followed by its own fields.
|
|
167
164
|
/// Fields set by `init` are not constructor parameters. `own` is the unlowered
|
|
168
165
|
/// parse of the document (header intact); `merged` is the fallback.
|
|
169
166
|
pub fn ctorParams(own: &ast::Source, merged: &ast::Source, owner: &str) -> Vec<(String, String)> {
|
|
@@ -176,23 +173,6 @@ pub fn ctorParams(own: &ast::Source, merged: &ast::Source, owner: &str) -> Vec<(
|
|
|
176
173
|
let Some(en) = find(own).or_else(|| find(merged)) else { return Vec::new() };
|
|
177
174
|
let v = &en.variants[0];
|
|
178
175
|
let mut out = Vec::new();
|
|
179
|
-
for (tname, exposed) in en.embedFields() {
|
|
180
|
-
for f in exposed {
|
|
181
|
-
let ty = [own, merged]
|
|
182
|
-
.into_iter()
|
|
183
|
-
.find_map(|src| {
|
|
184
|
-
src.items.iter().find_map(|i| match i {
|
|
185
|
-
ast::Item::Enum(e) if &e.name == tname => e.variants.first().and_then(|v| {
|
|
186
|
-
let k = v.field_names.iter().position(|n| n == f)?;
|
|
187
|
-
v.fields.get(k).map(typeText)
|
|
188
|
-
}),
|
|
189
|
-
_ => None,
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
.unwrap_or_default();
|
|
193
|
-
out.push((f.clone(), ty));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
176
|
for (i, name) in v.field_names.iter().enumerate() {
|
|
197
177
|
out.push((name.clone(), v.fields.get(i).map(typeText).unwrap_or_default()));
|
|
198
178
|
}
|
|
@@ -646,8 +626,6 @@ mod tests {
|
|
|
646
626
|
is_extern: false,
|
|
647
627
|
is_export: false,
|
|
648
628
|
is_test: false,
|
|
649
|
-
is_override: false,
|
|
650
|
-
is_abstract: false,
|
|
651
629
|
is_fip: false,
|
|
652
630
|
has_self: false,
|
|
653
631
|
params: vec![],
|
compiler/lsp/src/main.rs
CHANGED
|
@@ -207,7 +207,6 @@ impl LanguageServer for Backend {
|
|
|
207
207
|
..Default::default()
|
|
208
208
|
}),
|
|
209
209
|
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
|
210
|
-
definition_provider: Some(OneOf::Left(true)),
|
|
211
210
|
..Default::default()
|
|
212
211
|
},
|
|
213
212
|
server_info: Some(ServerInfo {
|
|
@@ -255,43 +254,16 @@ impl LanguageServer for Backend {
|
|
|
255
254
|
|
|
256
255
|
async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
|
|
257
256
|
let p = params.text_document_position_params;
|
|
258
|
-
let Ok(path) = p.text_document.uri.to_file_path() else {
|
|
259
|
-
return Ok(None);
|
|
260
|
-
};
|
|
261
257
|
let Some(text) = self.docs.lock().await.get(&p.text_document.uri).cloned() else {
|
|
262
258
|
return Ok(None);
|
|
263
259
|
};
|
|
264
|
-
Ok(hoverAt(&
|
|
260
|
+
Ok(hoverAt(&text, p.position.line as usize, p.position.character as usize)
|
|
265
261
|
.map(|s| Hover {
|
|
266
262
|
contents: HoverContents::Scalar(MarkedString::String(s)),
|
|
267
263
|
range: None,
|
|
268
264
|
}))
|
|
269
265
|
}
|
|
270
266
|
|
|
271
|
-
async fn goto_definition(
|
|
272
|
-
&self,
|
|
273
|
-
params: GotoDefinitionParams,
|
|
274
|
-
) -> RpcResult<Option<GotoDefinitionResponse>> {
|
|
275
|
-
let p = params.text_document_position_params;
|
|
276
|
-
let Ok(path) = p.text_document.uri.to_file_path() else {
|
|
277
|
-
return Ok(None);
|
|
278
|
-
};
|
|
279
|
-
let Some(text) = self.docs.lock().await.get(&p.text_document.uri).cloned() else {
|
|
280
|
-
return Ok(None);
|
|
281
|
-
};
|
|
282
|
-
let Some((file, l, c)) = definitionAt(&path, &self.libPath, &text, p.position.line as usize, p.position.character as usize) else {
|
|
283
|
-
return Ok(None);
|
|
284
|
-
};
|
|
285
|
-
let Ok(uri) = Url::from_file_path(&file) else {
|
|
286
|
-
return Ok(None);
|
|
287
|
-
};
|
|
288
|
-
let pos = Position::new(l, c);
|
|
289
|
-
Ok(Some(GotoDefinitionResponse::Scalar(Location {
|
|
290
|
-
uri,
|
|
291
|
-
range: Range { start: pos, end: pos },
|
|
292
|
-
})))
|
|
293
|
-
}
|
|
294
|
-
|
|
295
267
|
async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
|
|
296
268
|
let uri = params.text_document_position.text_document.uri;
|
|
297
269
|
let pos = params.text_document_position.position;
|
|
@@ -389,95 +361,6 @@ fn memberCompletionFor(
|
|
|
389
361
|
items
|
|
390
362
|
}
|
|
391
363
|
|
|
392
|
-
/// What the cursor is on, for hover / go-to-definition.
|
|
393
|
-
enum Target {
|
|
394
|
-
/// `recv.name` copied into the receiver's record from an embedded record `from`
|
|
395
|
-
/// (`struct World(Stage(camScale), ...)`). `loc` is the original's file, 1-based
|
|
396
|
-
/// line and column, for methods; fields point at the embedded record's name.
|
|
397
|
-
Inherited { from: String, name: String, loc: Option<(String, usize, usize)> },
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/// The embedded record (searched through `owner`'s embed chain) that `owner`'s
|
|
401
|
-
/// field or method `word` was copied from, if it was inherited.
|
|
402
|
-
fn inheritedFrom(
|
|
403
|
-
merged: &ast::Source,
|
|
404
|
-
path: &Path,
|
|
405
|
-
canonText: &(PathBuf, String),
|
|
406
|
-
owner: &str,
|
|
407
|
-
word: &str,
|
|
408
|
-
) -> Option<Target> {
|
|
409
|
-
// The merged program has its embed lists lowered away; read the headers from
|
|
410
|
-
// the unlowered sources (the entry buffer, then every file that declares a fn).
|
|
411
|
-
let mut files: Vec<String> = vec![path.to_string_lossy().into_owned()];
|
|
412
|
-
for i in &merged.items {
|
|
413
|
-
if let ast::Item::Fn(f) = i {
|
|
414
|
-
if !files.contains(&f.file) {
|
|
415
|
-
files.push(f.file.clone());
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
let mut headers: HashMap<String, Vec<String>> = HashMap::new();
|
|
420
|
-
for file in files {
|
|
421
|
-
let p = PathBuf::from(&file);
|
|
422
|
-
let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canonText.0 {
|
|
423
|
-
canonText.1.clone()
|
|
424
|
-
} else {
|
|
425
|
-
match std::fs::read_to_string(&p) {
|
|
426
|
-
Ok(c) => c,
|
|
427
|
-
Err(_) => continue,
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
let tree = Backend::parseTree(&content);
|
|
431
|
-
let src = plum_core::AstParser::new(&content).parseSourceRaw(tree.root_node());
|
|
432
|
-
for i in &src.items {
|
|
433
|
-
if let ast::Item::Enum(e) = i {
|
|
434
|
-
if e.isStruct() {
|
|
435
|
-
headers.insert(e.name.clone(), e.structInfo().map(|i| i.embedFields.iter().map(|(n, _)| n.clone()).collect()).unwrap_or_default());
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
let mut chain: Vec<String> = Vec::new();
|
|
441
|
-
let mut todo: Vec<String> = headers.get(owner)?.clone();
|
|
442
|
-
while !todo.is_empty() {
|
|
443
|
-
let n = todo.remove(0);
|
|
444
|
-
if headers.contains_key(&n) && !chain.contains(&n) {
|
|
445
|
-
todo.extend(headers[&n].clone());
|
|
446
|
-
chain.push(n);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
let method = |t: &str| {
|
|
450
|
-
merged.items.iter().find_map(|i| match i {
|
|
451
|
-
ast::Item::Fn(f) if f.type_param.as_deref() == Some(t) && f.name == word => Some(f),
|
|
452
|
-
_ => None,
|
|
453
|
-
})
|
|
454
|
-
};
|
|
455
|
-
let mine = method(owner);
|
|
456
|
-
for name in &chain {
|
|
457
|
-
let Some(e) = merged.items.iter().find_map(|i| match i {
|
|
458
|
-
ast::Item::Enum(e) if e.name == *name && e.isStruct() => Some(e),
|
|
459
|
-
_ => None,
|
|
460
|
-
}) else {
|
|
461
|
-
continue;
|
|
462
|
-
};
|
|
463
|
-
if let (Some(m), Some(orig)) = (mine, method(name)) {
|
|
464
|
-
if m.file == orig.file && m.line == orig.line && m.column == orig.column {
|
|
465
|
-
return Some(Target::Inherited {
|
|
466
|
-
from: name.clone(),
|
|
467
|
-
name: word.to_string(),
|
|
468
|
-
loc: Some((orig.file.clone(), orig.line, orig.column)),
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if mine.is_none()
|
|
473
|
-
&& e.variants.first().is_some_and(|v| v.field_names.iter().any(|n| n == word))
|
|
474
|
-
{
|
|
475
|
-
return Some(Target::Inherited { from: name.clone(), name: word.to_string(), loc: None });
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
None
|
|
479
|
-
}
|
|
480
|
-
|
|
481
364
|
fn identAt(line: &str, col: usize) -> Option<(usize, usize)> {
|
|
482
365
|
let b = line.as_bytes();
|
|
483
366
|
let isId = |c: u8| c.is_ascii_alphanumeric() || c == b'_';
|
|
@@ -523,64 +406,8 @@ fn mergedFn<'a>(merged: &'a ast::Source, own: Option<&'a ast::Fn>) -> Option<&'a
|
|
|
523
406
|
.or(Some(o))
|
|
524
407
|
}
|
|
525
408
|
|
|
526
|
-
fn text_override(path: &Path, text: &str) -> (PathBuf, String) {
|
|
527
|
-
(std::fs::canonicalize(path).unwrap_or(path.to_path_buf()), text.to_string())
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
fn resolveTarget(
|
|
531
|
-
path: &Path,
|
|
532
|
-
libPath: &Path,
|
|
533
|
-
text: &str,
|
|
534
|
-
line0: usize,
|
|
535
|
-
col: usize,
|
|
536
|
-
) -> Option<Target> {
|
|
537
|
-
let lineText = text.lines().nth(line0)?;
|
|
538
|
-
let (s, e) = identAt(lineText, col)?;
|
|
539
|
-
let word = &lineText[s..e];
|
|
540
|
-
let canon = std::fs::canonicalize(path).unwrap_or(path.to_path_buf());
|
|
541
|
-
let mut overrides = HashMap::new();
|
|
542
|
-
overrides.insert(canon, text.to_string());
|
|
543
|
-
let merged = plum_core::loadAndMergeWithOverridesDiag(path, libPath, &overrides).ok()?;
|
|
544
|
-
if s > 0 && lineText.as_bytes()[s - 1] == b'.' {
|
|
545
|
-
let before = &lineText[..s - 1];
|
|
546
|
-
let rs = before
|
|
547
|
-
.rfind(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
|
|
548
|
-
.map(|i| i + 1)
|
|
549
|
-
.unwrap_or(0);
|
|
550
|
-
let receiver = &before[rs..];
|
|
551
|
-
let tree = Backend::parseTree(text);
|
|
552
|
-
let own = plum_core::AstParser::new(text).parseSourceRaw(tree.root_node());
|
|
553
|
-
let (global_env, methods, enum_variants) = plum_checker::buildGlobalTables(&merged);
|
|
554
|
-
let enum_params = plum_checker::EnumParams::new();
|
|
555
|
-
let min_required = plum_checker::buildMinRequiredArgs(&merged);
|
|
556
|
-
let primitive_generics = plum_checker::buildPrimitiveGenerics(&merged);
|
|
557
|
-
let statics = plum_checker::buildStaticMethods(&merged);
|
|
558
|
-
let ctx = plum_checker::CheckCtx {
|
|
559
|
-
methods: &methods,
|
|
560
|
-
enum_variants: &enum_variants,
|
|
561
|
-
enum_params: &enum_params,
|
|
562
|
-
min_required: &min_required,
|
|
563
|
-
primitive_generics: &primitive_generics,
|
|
564
|
-
statics: Some(&statics),
|
|
565
|
-
};
|
|
566
|
-
let f = mergedFn(&merged, completion::enclosingFn(&own, line0 + 1));
|
|
567
|
-
let ty = completion::receiverType(f, receiver, &global_env, &ctx)?;
|
|
568
|
-
let owner = plum_checker::methodReceiverName(&ty)?;
|
|
569
|
-
if let Some(t) = inheritedFrom(&merged, path, &text_override(path, text), &owner, word) {
|
|
570
|
-
return Some(t);
|
|
571
|
-
}
|
|
572
|
-
return None;
|
|
573
|
-
}
|
|
574
|
-
None
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
fn hoverAt(
|
|
409
|
+
fn hoverAt(text: &str, line0: usize, col: usize) -> Option<String> {
|
|
578
|
-
|
|
410
|
+
recordHover(text, line0, col)
|
|
579
|
-
return Some(h);
|
|
580
|
-
}
|
|
581
|
-
match resolveTarget(path, libPath, text, line0, col)? {
|
|
582
|
-
Target::Inherited { from, name, .. } => Some(format!("{name} (from {from})")),
|
|
583
|
-
}
|
|
584
411
|
}
|
|
585
412
|
|
|
586
413
|
/// Hover on a struct/record name in this document: its constructor parameters.
|
|
@@ -601,98 +428,6 @@ fn recordHover(text: &str, line0: usize, col: usize) -> Option<String> {
|
|
|
601
428
|
Some(format!("{word}({})", list.join(", ")))
|
|
602
429
|
}
|
|
603
430
|
|
|
604
|
-
/// Where an inherited method (or, for a field, the embedded struct) is declared.
|
|
605
|
-
fn definitionAt(
|
|
606
|
-
path: &Path,
|
|
607
|
-
libPath: &Path,
|
|
608
|
-
text: &str,
|
|
609
|
-
line0: usize,
|
|
610
|
-
col: usize,
|
|
611
|
-
) -> Option<(PathBuf, u32, u32)> {
|
|
612
|
-
match resolveTarget(path, libPath, text, line0, col)? {
|
|
613
|
-
Target::Inherited { from, name, loc } => {
|
|
614
|
-
inheritedDefinition(path, libPath, text, &from, &name, loc.as_ref())
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/// Where an inherited method (or, for a field, the embedded record) is declared.
|
|
620
|
-
fn inheritedDefinition(
|
|
621
|
-
path: &Path,
|
|
622
|
-
libPath: &Path,
|
|
623
|
-
text: &str,
|
|
624
|
-
from: &str,
|
|
625
|
-
name: &str,
|
|
626
|
-
loc: Option<&(String, usize, usize)>,
|
|
627
|
-
) -> Option<(PathBuf, u32, u32)> {
|
|
628
|
-
let canon = std::fs::canonicalize(path).unwrap_or(path.to_path_buf());
|
|
629
|
-
let mut overrides = HashMap::new();
|
|
630
|
-
overrides.insert(canon.clone(), text.to_string());
|
|
631
|
-
let merged = plum_core::loadAndMergeWithOverridesDiag(path, libPath, &overrides).ok()?;
|
|
632
|
-
let (file, line, needle) = match loc {
|
|
633
|
-
Some((f, l, _)) => (f.clone(), *l, format!("fun {name}")),
|
|
634
|
-
None => {
|
|
635
|
-
let l = merged.items.iter().find_map(|i| match i {
|
|
636
|
-
ast::Item::Enum(e) if e.name == from && e.isStruct() => Some(e.line),
|
|
637
|
-
_ => None,
|
|
638
|
-
})?;
|
|
639
|
-
return recordNameAt(&merged, path, &canon, text, from, l);
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
let mut p = PathBuf::from(&file);
|
|
643
|
-
let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canon {
|
|
644
|
-
p = path.to_path_buf();
|
|
645
|
-
text.to_string()
|
|
646
|
-
} else {
|
|
647
|
-
std::fs::read_to_string(&p).ok()?
|
|
648
|
-
};
|
|
649
|
-
let ln = content.lines().nth(line.checked_sub(1)?)?;
|
|
650
|
-
let at = ln.find(&needle)? + "fun ".len();
|
|
651
|
-
Some((p, line as u32 - 1, at as u32))
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/// The `struct <from>` declaration line (searched in the entry file, then imports).
|
|
655
|
-
fn recordNameAt(
|
|
656
|
-
merged: &ast::Source,
|
|
657
|
-
path: &Path,
|
|
658
|
-
canon: &Path,
|
|
659
|
-
text: &str,
|
|
660
|
-
from: &str,
|
|
661
|
-
_line: usize,
|
|
662
|
-
) -> Option<(PathBuf, u32, u32)> {
|
|
663
|
-
let mut files: Vec<String> = vec![path.to_string_lossy().into_owned()];
|
|
664
|
-
for i in &merged.items {
|
|
665
|
-
if let ast::Item::Fn(f) = i {
|
|
666
|
-
if !files.contains(&f.file) {
|
|
667
|
-
files.push(f.file.clone());
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
let headers = [format!("struct {from}")];
|
|
672
|
-
for file in files {
|
|
673
|
-
let p = PathBuf::from(&file);
|
|
674
|
-
let content = if std::fs::canonicalize(&p).unwrap_or(p.clone()) == canon {
|
|
675
|
-
text.to_string()
|
|
676
|
-
} else {
|
|
677
|
-
match std::fs::read_to_string(&p) {
|
|
678
|
-
Ok(c) => c,
|
|
679
|
-
Err(_) => continue,
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
for (n, l) in content.lines().enumerate() {
|
|
683
|
-
for header in &headers {
|
|
684
|
-
if let Some(c) = l.find(header.as_str()) {
|
|
685
|
-
let end = c + header.len();
|
|
686
|
-
if l[end..].chars().next().map_or(true, |ch| !(ch.is_ascii_alphanumeric() || ch == '_')) {
|
|
687
|
-
return Some((p, n as u32, (c + "struct ".len()) as u32));
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
None
|
|
694
|
-
}
|
|
695
|
-
|
|
696
431
|
#[tokio::main]
|
|
697
432
|
async fn main() {
|
|
698
433
|
// `--lib-path` mirrors `plum-cli`'s own required flag (see
|
|
@@ -728,36 +463,16 @@ mod tests {
|
|
|
728
463
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
|
|
729
464
|
}
|
|
730
465
|
|
|
731
|
-
// `Entity` is a base struct; `House` embeds it and is its subtype.
|
|
732
|
-
const TRAIT_SRC: &str = "module t\nimport std/Int\nimport std/Bool\n\nstruct Vec2(x: Int, y: Int)\n\nstruct Entity(pos: Vec2, hp: Int) =\n fun alive(self) -> Bool = self.hp > 0\n\nstruct House(Entity(pos, hp), rooms: Int)\n\nfun main() =\n h := House(pos: Vec2(x: 1, y: 2), hp: 5, rooms: 3)\n h.pos\n e: Entity = h\n e.zzzzzz\n";
|
|
733
|
-
|
|
734
|
-
fn traitFile(tag: &str) -> PathBuf {
|
|
735
|
-
let dir = std::env::temp_dir().join(format!("plum-lsp-trait-{}-{}", tag, std::process::id()));
|
|
736
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
737
|
-
let path = dir.join("t.plum");
|
|
738
|
-
std::fs::write(&path, TRAIT_SRC).unwrap();
|
|
739
|
-
path
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
#[test]
|
|
743
|
-
fn hover_embedded_field() {
|
|
744
|
-
let path = traitFile("hover");
|
|
745
|
-
// line 12 (0-based 11): ` h.pos`, cursor on `pos`.
|
|
746
|
-
let h = hoverAt(&path, &repoRoot(), TRAIT_SRC, 13, 5).expect("hover");
|
|
747
|
-
assert!(h.contains("pos (from Entity)"), "{h}");
|
|
748
|
-
}
|
|
749
|
-
|
|
750
466
|
#[test]
|
|
751
467
|
fn hover_record_shows_ctor_params() {
|
|
752
|
-
let src = "module t\n\nstruct
|
|
468
|
+
let src = "module t\n\nstruct Ship(x: Int, y: Int, tex: Int) =\n fun init(self) =\n self.z: Int = 1\n";
|
|
753
|
-
let path = traitFile("hoverrec");
|
|
754
|
-
let h = hoverAt(
|
|
469
|
+
let h = hoverAt(src, 2, 8).expect("hover");
|
|
755
470
|
assert_eq!(h, "Ship(x: Int, y: Int, tex: Int)");
|
|
756
471
|
}
|
|
757
472
|
|
|
758
473
|
#[test]
|
|
759
474
|
fn complete_ctor_offers_only_ctor_params() {
|
|
760
|
-
let src = "module t\n\nstruct
|
|
475
|
+
let src = "module t\n\nstruct Ship(x: Int, y: Int, tex: Int)\n";
|
|
761
476
|
let tree = Backend::parseTree(src);
|
|
762
477
|
let ast = plum_core::AstParser::new(src).parseSourceRaw(tree.root_node());
|
|
763
478
|
let labels: Vec<String> = completion::ctorArgItems(&ast, &ast, "Ship").into_iter().map(|i| i.label).collect();
|
|
@@ -766,51 +481,6 @@ mod tests {
|
|
|
766
481
|
assert_eq!(completion::enclosingCtor(" f(Vec(x: 1), "), None);
|
|
767
482
|
}
|
|
768
483
|
|
|
769
|
-
#[test]
|
|
770
|
-
fn complete_base_value() {
|
|
771
|
-
let path = traitFile("complete");
|
|
772
|
-
let items = memberCompletionFor(&path, &repoRoot(), TRAIT_SRC, 16, "e");
|
|
773
|
-
let labels: Vec<&str> = items.iter().map(|i| i.label.as_str()).collect();
|
|
774
|
-
for want in ["pos", "hp", "alive"] {
|
|
775
|
-
assert!(labels.contains(&want), "missing {want} in {labels:?}");
|
|
776
|
-
}
|
|
777
|
-
assert!(!labels.iter().any(|l| l.starts_with("__")), "{labels:?}");
|
|
778
|
-
let rec = memberCompletionFor(&path, &repoRoot(), TRAIT_SRC, 14, "h");
|
|
779
|
-
let rl: Vec<&str> = rec.iter().map(|i| i.label.as_str()).collect();
|
|
780
|
-
for want in ["pos", "hp", "rooms", "alive"] {
|
|
781
|
-
assert!(rl.contains(&want), "missing {want} in {rl:?}");
|
|
782
|
-
}
|
|
783
|
-
assert!(!rl.iter().any(|l| l.starts_with("__")), "{rl:?}");
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
#[test]
|
|
787
|
-
fn goto_embedded_field() {
|
|
788
|
-
let path = traitFile("goto");
|
|
789
|
-
let (f, l, c) = definitionAt(&path, &repoRoot(), TRAIT_SRC, 13, 5).expect("def");
|
|
790
|
-
assert_eq!(f, path);
|
|
791
|
-
assert_eq!((l, c), (6, 7), "`Entity` in `struct Entity(pos: ...` (fields point at the embedded struct)");
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const EMBED_SRC: &str = "module t\nimport std/Int\n\nstruct Stage(camScale: Int) =\n fun init(self) =\n self.camX: Int = 0\n\n fun act(self, dt: Int) =\n self.camX = self.camX + dt\n\nstruct World(Stage(camScale), score: Int)\n\nfun main() =\n w := World(camScale: 2, score: 1)\n w.act(1)\n x := w.camX\n";
|
|
795
|
-
|
|
796
|
-
#[test]
|
|
797
|
-
fn hover_inherited_method() {
|
|
798
|
-
let path = traitFile("hoverinh");
|
|
799
|
-
// line 14 (0-based): ` w.act(1)`, cursor on `act`.
|
|
800
|
-
let h = hoverAt(&path, &repoRoot(), EMBED_SRC, 14, 5).expect("hover");
|
|
801
|
-
assert!(h.contains("from Stage"), "{h}");
|
|
802
|
-
let f = hoverAt(&path, &repoRoot(), EMBED_SRC, 15, 11).expect("hover field");
|
|
803
|
-
assert!(f.contains("from Stage"), "{f}");
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
#[test]
|
|
807
|
-
fn goto_inherited_method() {
|
|
808
|
-
let path = traitFile("gotoinh");
|
|
809
|
-
let (f, l, c) = definitionAt(&path, &repoRoot(), EMBED_SRC, 14, 5).expect("def");
|
|
810
|
-
assert_eq!(f, path);
|
|
811
|
-
assert_eq!((l, c), (7, 6), "act in ` fun act(self, ...` of Stage");
|
|
812
|
-
}
|
|
813
|
-
|
|
814
484
|
/// The whole diagnostic path (`publishAll`: syntax diagnostics, then the
|
|
815
485
|
/// two-pass checker) must find nothing wrong in the sequences example.
|
|
816
486
|
#[test]
|
|
@@ -1033,7 +703,6 @@ mod tests {
|
|
|
1033
703
|
"then",
|
|
1034
704
|
"true",
|
|
1035
705
|
"false",
|
|
1036
|
-
"override",
|
|
1037
706
|
] {
|
|
1038
707
|
assert!(labels.iter().any(|l| l == want), "missing {want}");
|
|
1039
708
|
}
|
|
@@ -1048,6 +717,8 @@ mod tests {
|
|
|
1048
717
|
"indexed",
|
|
1049
718
|
"case",
|
|
1050
719
|
"trait",
|
|
720
|
+
"override",
|
|
721
|
+
"abstract",
|
|
1051
722
|
"implements",
|
|
1052
723
|
"error",
|
|
1053
724
|
"catch",
|
compiler/tooling/tree-sitter-plum/grammar.js
CHANGED
|
@@ -162,29 +162,13 @@ module.exports = grammar({
|
|
|
162
162
|
),
|
|
163
163
|
|
|
164
164
|
// `struct Vec2(x: Float, y: Float)` / `struct Pair[K, V](key: K, val: V)`:
|
|
165
|
-
// the one product type (generics,
|
|
165
|
+
// the one product type (generics, `mut` fields, no/empty
|
|
166
166
|
// parens); the compiler picks the layout.
|
|
167
167
|
// Optional `=` body holds consts and methods.
|
|
168
168
|
struct: ($) => productBody($, "struct"),
|
|
169
169
|
|
|
170
|
-
// Constructor fields
|
|
170
|
+
// Constructor fields: `(x: Int, k: Int)`.
|
|
171
|
-
// `(E)`, `(k: Int)`. Embeds always come first; `(k: Int, E)` is a syntax error.
|
|
172
|
-
_ctor_fields: ($) =>
|
|
173
|
-
choice(
|
|
174
|
-
seq(
|
|
175
|
-
commaSep1(field("embed", $.embed_spec)),
|
|
176
|
-
|
|
171
|
+
_ctor_fields: ($) => commaSep1(field("field", alias($.ctor_field, $.enum_named_field))),
|
|
177
|
-
),
|
|
178
|
-
commaSep1(field("field", alias($.ctor_field, $.enum_named_field))),
|
|
179
|
-
),
|
|
180
|
-
|
|
181
|
-
// A struct embedded in a struct header, optionally selecting its exposed fields:
|
|
182
|
-
// `Actor` or `Actor(x, y)`.
|
|
183
|
-
embed_spec: ($) =>
|
|
184
|
-
seq(
|
|
185
|
-
field("name", $.type_identifier),
|
|
186
|
-
optional(seq("(", commaSep1(field("exposed", $.var_identifier)), ")")),
|
|
187
|
-
),
|
|
188
172
|
|
|
189
173
|
// `primitive Name =` + consts, statics and (for a compiler built-in such as
|
|
190
174
|
// `Int`, only in std) methods: declares a built-in's API, else a stateless
|
|
@@ -248,7 +232,7 @@ module.exports = grammar({
|
|
|
248
232
|
// error either way), not the grammar, to keep this rule simple.
|
|
249
233
|
// `export fun foo(...) = ...` is a normal function that is also exported from
|
|
250
234
|
// the wasm module under its own name, with the `extern` boundary ABI.
|
|
251
|
-
optional(choice(field("externKw", "extern"), field("exportKw", "export")
|
|
235
|
+
optional(choice(field("externKw", "extern"), field("exportKw", "export"))),
|
|
252
236
|
// `fip fun`: a compile-time guarantee that the body's updates run in place.
|
|
253
237
|
optional(field("fipKw", "fip")),
|
|
254
238
|
field("kw", "fun"),
|
|
@@ -806,7 +790,7 @@ function sep1(rule, separator) {
|
|
|
806
790
|
return seq(rule, repeat(seq(separator, rule)));
|
|
807
791
|
}
|
|
808
792
|
// Shape of `struct`: name, generics, optional constructor
|
|
809
|
-
// fields
|
|
793
|
+
// fields, optional `=` body of methods and consts.
|
|
810
794
|
function productBody($, keyword) {
|
|
811
795
|
return seq(
|
|
812
796
|
keyword,
|
compiler/tooling/tree-sitter-plum/queries/plum/format.scm
CHANGED
|
@@ -128,9 +128,7 @@
|
|
|
128
128
|
|
|
129
129
|
(fn "extern" @append_space)
|
|
130
130
|
|
|
131
|
-
(fn "override" @append_space)
|
|
132
131
|
|
|
133
|
-
(fn "abstract" @append_space)
|
|
134
132
|
|
|
135
133
|
(fn "fip" @append_space)
|
|
136
134
|
|
compiler/tooling/tree-sitter-plum/queries/plum/highlights.scm
CHANGED
|
@@ -93,8 +93,6 @@
|
|
|
93
93
|
"fun"
|
|
94
94
|
"extern"
|
|
95
95
|
"export"
|
|
96
|
-
"override"
|
|
97
|
-
"abstract"
|
|
98
96
|
"fip"
|
|
99
97
|
"fn"
|
|
100
98
|
"test"
|
compiler/tooling/tree-sitter-plum/src/grammar.json
CHANGED
|
Binary file
|
compiler/tooling/tree-sitter-plum/src/node-types.json
CHANGED
|
Binary file
|
compiler/tooling/tree-sitter-plum/src/parser.c
CHANGED
|
Binary file
|
compiler/tooling/tree-sitter-plum/test/corpus/fip.txt
CHANGED
|
@@ -20,11 +20,11 @@ fip fun step(n: Int) -> Int = n
|
|
|
20
20
|
(var_identifier)))))
|
|
21
21
|
|
|
22
22
|
================================================================================
|
|
23
|
-
|
|
23
|
+
fip fun in a struct body
|
|
24
24
|
================================================================================
|
|
25
25
|
|
|
26
26
|
struct P(x: Int, fip: Int) =
|
|
27
|
-
|
|
27
|
+
fip fun size(self) -> Int = self.fip
|
|
28
28
|
|
|
29
29
|
--------------------------------------------------------------------------------
|
|
30
30
|
|
compiler/tooling/tree-sitter-plum/test/corpus/function.txt
CHANGED
|
@@ -465,7 +465,8 @@ fun main() -> Int =
|
|
|
465
465
|
(var_identifier)))))))))))))
|
|
466
466
|
|
|
467
467
|
================================================================================
|
|
468
|
-
override
|
|
468
|
+
override is removed
|
|
469
|
+
:error
|
|
469
470
|
================================================================================
|
|
470
471
|
|
|
471
472
|
struct Rr(a: Int) =
|
|
@@ -473,22 +474,11 @@ struct Rr(a: Int) =
|
|
|
473
474
|
|
|
474
475
|
--------------------------------------------------------------------------------
|
|
475
476
|
|
|
476
|
-
(source
|
|
477
|
-
(struct
|
|
478
|
-
(type_identifier)
|
|
479
|
-
(enum_named_field
|
|
480
|
-
(var_identifier)
|
|
481
|
-
(type
|
|
482
|
-
(type_identifier)))
|
|
483
|
-
(fn
|
|
484
|
-
(fn_identifier)
|
|
485
|
-
|
|
477
|
+
(ERROR)
|
|
486
|
-
(expression
|
|
487
|
-
(primary_expression
|
|
488
|
-
(integer))))))
|
|
489
478
|
|
|
490
479
|
================================================================================
|
|
491
|
-
abstract
|
|
480
|
+
abstract is removed
|
|
481
|
+
:error
|
|
492
482
|
================================================================================
|
|
493
483
|
|
|
494
484
|
struct Aa(a: Int) =
|
|
@@ -496,18 +486,7 @@ struct Aa(a: Int) =
|
|
|
496
486
|
|
|
497
487
|
--------------------------------------------------------------------------------
|
|
498
488
|
|
|
499
|
-
(source
|
|
500
|
-
(struct
|
|
501
|
-
(type_identifier)
|
|
502
|
-
(enum_named_field
|
|
503
|
-
(var_identifier)
|
|
504
|
-
(type
|
|
505
|
-
(type_identifier)))
|
|
506
|
-
(fn
|
|
507
|
-
(fn_identifier)
|
|
508
|
-
|
|
489
|
+
(ERROR)
|
|
509
|
-
(type
|
|
510
|
-
(type_identifier)))))
|
|
511
490
|
|
|
512
491
|
================================================================================
|
|
513
492
|
function - match body
|
compiler/tooling/tree-sitter-plum/test/corpus/struct_unified.txt
CHANGED
|
@@ -17,22 +17,15 @@ struct Box[T](value: T)
|
|
|
17
17
|
(type_identifier)))))
|
|
18
18
|
|
|
19
19
|
================================================================================
|
|
20
|
-
|
|
20
|
+
embedding is removed
|
|
21
|
+
:error
|
|
21
22
|
================================================================================
|
|
22
23
|
|
|
23
24
|
struct House(Actor, k: Int)
|
|
24
25
|
|
|
25
26
|
--------------------------------------------------------------------------------
|
|
26
27
|
|
|
27
|
-
(
|
|
28
|
+
(ERROR)
|
|
28
|
-
(struct
|
|
29
|
-
(type_identifier)
|
|
30
|
-
(embed_spec
|
|
31
|
-
(type_identifier))
|
|
32
|
-
(enum_named_field
|
|
33
|
-
(var_identifier)
|
|
34
|
-
(type
|
|
35
|
-
(type_identifier)))))
|
|
36
29
|
|
|
37
30
|
================================================================================
|
|
38
31
|
struct with mut field and methods
|
compiler/tooling/tree-sitter-plum/test/corpus/trait.txt
CHANGED
|
@@ -11,46 +11,26 @@ trait X(a: Int) =
|
|
|
11
11
|
(ERROR)
|
|
12
12
|
|
|
13
13
|
================================================================================
|
|
14
|
-
|
|
14
|
+
embeds are removed
|
|
15
|
+
:error
|
|
15
16
|
================================================================================
|
|
16
17
|
|
|
17
18
|
struct House(Ent, Cl, k: Int)
|
|
18
19
|
|
|
19
20
|
--------------------------------------------------------------------------------
|
|
20
21
|
|
|
21
|
-
(
|
|
22
|
+
(ERROR)
|
|
22
|
-
(struct
|
|
23
|
-
(type_identifier)
|
|
24
|
-
(embed_spec
|
|
25
|
-
(type_identifier))
|
|
26
|
-
(embed_spec
|
|
27
|
-
(type_identifier))
|
|
28
|
-
(enum_named_field
|
|
29
|
-
(var_identifier)
|
|
30
|
-
(type
|
|
31
|
-
(type_identifier)))))
|
|
32
23
|
|
|
33
24
|
================================================================================
|
|
34
|
-
|
|
25
|
+
embed with exposed fields is removed
|
|
26
|
+
:error
|
|
35
27
|
================================================================================
|
|
36
28
|
|
|
37
29
|
struct Ship(Actor(x, y), Body, tex: Int)
|
|
38
30
|
|
|
39
31
|
--------------------------------------------------------------------------------
|
|
40
32
|
|
|
41
|
-
(
|
|
33
|
+
(ERROR)
|
|
42
|
-
(struct
|
|
43
|
-
(type_identifier)
|
|
44
|
-
(embed_spec
|
|
45
|
-
(type_identifier)
|
|
46
|
-
(var_identifier)
|
|
47
|
-
(var_identifier))
|
|
48
|
-
(embed_spec
|
|
49
|
-
(type_identifier))
|
|
50
|
-
(enum_named_field
|
|
51
|
-
(var_identifier)
|
|
52
|
-
(type
|
|
53
|
-
(type_identifier)))))
|
|
54
34
|
|
|
55
35
|
================================================================================
|
|
56
36
|
typed self field assignment
|
compiler/tooling/vscode-plum/syntaxes/plum.tmLanguage.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"keywords": {
|
|
40
40
|
"patterns": [
|
|
41
41
|
{
|
|
42
|
-
"match": "\\b(module|struct|enum|trait|primitive|fun|fn|extern|export|
|
|
42
|
+
"match": "\\b(module|struct|enum|trait|primitive|fun|fn|extern|export|fip|self|test)(?![A-Za-z0-9_])",
|
|
43
43
|
"name": "keyword.const.plum"
|
|
44
44
|
},
|
|
45
45
|
{
|
compiler/wasm-codegen/src/calls.rs
CHANGED
|
@@ -316,16 +316,6 @@ pub(crate) fn compileMethodCallAs(
|
|
|
316
316
|
.func_ids
|
|
317
317
|
.get(&key)
|
|
318
318
|
.ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
|
|
319
|
-
if !matches!(object, ast::Expr { kind: ast::ExprKind::TypeName(_), .. }) {
|
|
320
|
-
if let (Some(overs), Some(&recv)) = (
|
|
321
|
-
virtualOverriders(obj_ty, method),
|
|
322
|
-
ctx.locals.get(&virtRecvLocal(result_expr)),
|
|
323
|
-
) {
|
|
324
|
-
return compileVirtualCall(
|
|
325
|
-
object, method, call, &class_name, &overs, recv, result_expr, body, ctx, state,
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
319
|
// A "static"-style call (`Bool.parse("true")`, `Float.fromStr("3.14")`) on a
|
|
330
320
|
// self-less method declared inside a `type`/`enum` body — its receiver is a
|
|
331
321
|
// bare reference to the type's own name, not a real value. Every method still
|
|
@@ -354,73 +344,3 @@ pub(crate) fn compileMethodCallAs(
|
|
|
354
344
|
emitCall(func_idx, sig, call.tail, result_expr, body, ctx);
|
|
355
345
|
Ok(())
|
|
356
346
|
}
|
|
357
|
-
|
|
358
|
-
/// A call whose receiver's static type has overriders: the receiver is evaluated once
|
|
359
|
-
/// into `recv`, then for each overrider (deepest first) `ref.test S` selects
|
|
360
|
-
/// `ref.cast S` + `S.m`, else `T.m`. The arguments are compiled once per branch (only
|
|
361
|
-
/// one branch runs, so each is still evaluated exactly once, after the receiver).
|
|
362
|
-
#[allow(clippy::too_many_arguments)]
|
|
363
|
-
pub(crate) fn compileVirtualCall(
|
|
364
|
-
object: &ast::Expr,
|
|
365
|
-
method: &str,
|
|
366
|
-
call: &ast::FnCall,
|
|
367
|
-
class_name: &str,
|
|
368
|
-
overs: &[String],
|
|
369
|
-
recv: u32,
|
|
370
|
-
result_expr: &ast::Expr,
|
|
371
|
-
body: &mut Vec<u8>,
|
|
372
|
-
ctx: &LocalCtx,
|
|
373
|
-
state: &mut ModuleState,
|
|
374
|
-
) -> Result<(), String> {
|
|
375
|
-
let baseKey = format!("{}::{}", class_name, method);
|
|
376
|
-
let ret = ctx
|
|
377
|
-
.func_sigs
|
|
378
|
-
.get(&baseKey)
|
|
379
|
-
.map(|s| s.ret.clone())
|
|
380
|
-
.ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
|
|
381
|
-
let bt = match ret.len() {
|
|
382
|
-
0 => BlockType::Empty,
|
|
383
|
-
1 => BlockType::Result(ret[0]),
|
|
384
|
-
_ => {
|
|
385
|
-
return Err(format!(
|
|
386
|
-
"codegen: virtual method '{}.{}' returning a multi-value (flat) struct is not supported",
|
|
387
|
-
class_name, method
|
|
388
|
-
))
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
compileExpr(object, body, ctx, state)?;
|
|
392
|
-
Instruction::LocalSet(recv).encode(body);
|
|
393
|
-
let mut guards = Vec::new();
|
|
394
|
-
for s in overs {
|
|
395
|
-
let key = format!("{}::{}", s, method);
|
|
396
|
-
let (Some(&idx), Some(sig), Some(&sidx)) = (
|
|
397
|
-
ctx.func_ids.get(&key),
|
|
398
|
-
ctx.func_sigs.get(&key),
|
|
399
|
-
ctx.gc_types.class_type_idx.get(s),
|
|
400
|
-
) else {
|
|
401
|
-
return Err(format!("codegen: unknown overrider '{}.{}'", s, method));
|
|
402
|
-
};
|
|
403
|
-
Instruction::LocalGet(recv).encode(body);
|
|
404
|
-
Instruction::RefTestNonNull(HeapType::Concrete(sidx)).encode(body);
|
|
405
|
-
Instruction::If(bt).encode(body);
|
|
406
|
-
guards.push(TailDepth::enter(&ctx.tail_depth));
|
|
407
|
-
Instruction::LocalGet(recv).encode(body);
|
|
408
|
-
Instruction::RefCastNonNull(HeapType::Concrete(sidx)).encode(body);
|
|
409
|
-
compileCallArgs(&key, &call.args, sig, body, ctx, state)?;
|
|
410
|
-
emitCall(idx, sig, call.tail, result_expr, body, ctx);
|
|
411
|
-
Instruction::Else.encode(body);
|
|
412
|
-
}
|
|
413
|
-
let idx = *ctx
|
|
414
|
-
.func_ids
|
|
415
|
-
.get(&baseKey)
|
|
416
|
-
.ok_or_else(|| format!("codegen: unknown method '{}.{}'", class_name, method))?;
|
|
417
|
-
let sig = &ctx.func_sigs[&baseKey];
|
|
418
|
-
Instruction::LocalGet(recv).encode(body);
|
|
419
|
-
compileCallArgs(&baseKey, &call.args, sig, body, ctx, state)?;
|
|
420
|
-
emitCall(idx, sig, call.tail, result_expr, body, ctx);
|
|
421
|
-
for _ in overs {
|
|
422
|
-
Instruction::End.encode(body);
|
|
423
|
-
}
|
|
424
|
-
drop(guards);
|
|
425
|
-
Ok(())
|
|
426
|
-
}
|
compiler/wasm-codegen/src/context.rs
CHANGED
|
@@ -288,9 +288,6 @@ pub(crate) struct GenState {
|
|
|
288
288
|
pub(crate) value_helpers: HashMap<String, u32>,
|
|
289
289
|
/// Per-compile facts (the source file `x!` crash messages name).
|
|
290
290
|
pub(crate) err_info: ErrInfo,
|
|
291
|
-
/// `(static receiver type, method)` -> the strict subtypes overriding it, deepest
|
|
292
|
-
/// first (`plum_core::subtyping::overriders`); non-empty entries only.
|
|
293
|
-
pub(crate) overriders: HashMap<(String, String), Vec<String>>,
|
|
294
291
|
}
|
|
295
292
|
|
|
296
293
|
impl GenState {
|
compiler/wasm-codegen/src/driver.rs
CHANGED
|
@@ -65,23 +65,13 @@ pub(crate) fn resetGenState(source: &ast::Source) {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/// The lowered program the types are keyed by: every node id is unique
|
|
68
|
+
/// The lowered program the types are keyed by: every node id is unique.
|
|
69
|
-
/// overriders of each virtual method are recorded.
|
|
70
|
-
pub(crate) fn
|
|
69
|
+
pub(crate) fn checkNodeIds(source: &ast::Source) {
|
|
71
70
|
debug_assert!(
|
|
72
71
|
ast::duplicateId(source).is_none(),
|
|
73
72
|
"codegen: two nodes of the lowered program share {:?}",
|
|
74
73
|
ast::duplicateId(source)
|
|
75
74
|
);
|
|
76
|
-
withGen(|s| {
|
|
77
|
-
s.overriders = plum_core::subtyping::virtualMethods(source)
|
|
78
|
-
.into_iter()
|
|
79
|
-
.filter_map(|(t, m)| {
|
|
80
|
-
let o = plum_core::subtyping::overriders(source, &t, &m);
|
|
81
|
-
(!o.is_empty()).then_some(((t, m), o))
|
|
82
|
-
})
|
|
83
|
-
.collect();
|
|
84
|
-
});
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
impl<'a> Setup<'a> {
|
compiler/wasm-codegen/src/emit.rs
CHANGED
|
@@ -62,8 +62,6 @@ impl<'a> Emit<'a> {
|
|
|
62
62
|
is_extern: false,
|
|
63
63
|
is_export: false,
|
|
64
64
|
is_test: false,
|
|
65
|
-
is_override: false,
|
|
66
|
-
is_abstract: false,
|
|
67
65
|
is_fip: false,
|
|
68
66
|
has_self: true,
|
|
69
67
|
params: Vec::new(),
|
compiler/wasm-codegen/src/fnbody.rs
CHANGED
|
@@ -227,14 +227,6 @@ impl<'a> Collector<'a> {
|
|
|
227
227
|
ast::Expr { kind: ast::ExprKind::Attribute(a), .. } => {
|
|
228
228
|
self.walkExpr(&a.object);
|
|
229
229
|
if let ast::AttrKind::Method(call) = &a.attr {
|
|
230
|
-
// A virtual call (the receiver's static type has overriders) parks
|
|
231
|
-
// the receiver while its dynamic type is tested.
|
|
232
|
-
if !matches!(a.object, ast::Expr { kind: ast::ExprKind::TypeName(_), .. }) {
|
|
233
|
-
let oty = self.ty(&a.object);
|
|
234
|
-
if virtualOverriders(&oty, &call.name).is_some() {
|
|
235
|
-
self.bind(&virtRecvLocal(expr), oty);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
230
|
// A call through an fn-typed field needs a local to hold the
|
|
239
231
|
// closure (read twice: env, then table index). Reserved even if
|
|
240
232
|
// a same-named method wins at compile time (then left unused).
|
|
@@ -502,8 +494,7 @@ pub(crate) fn compileFnBodyImpl(
|
|
|
502
494
|
ast::FnBody::Block(block) => {
|
|
503
495
|
compileBlockAsFnBody(block, &mut body, &local_ctx, state, result_vt)?;
|
|
504
496
|
}
|
|
505
|
-
// `fns` excludes every `extern fun`;
|
|
497
|
+
// `fns` excludes every `extern fun`; the checker rejects any other bodiless fn.
|
|
506
|
-
// and never runs (a subtype overrides it).
|
|
507
498
|
ast::FnBody::Extern => Instruction::Unreachable.encode(&mut body),
|
|
508
499
|
}
|
|
509
500
|
|
compiler/wasm-codegen/src/gctypes.rs
CHANGED
|
@@ -204,46 +204,6 @@ pub(crate) fn arrayDepth(t: &PlumType) -> usize {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
/// Struct subtyping: base name -> the structs whose FIRST embed it is, from the lowered
|
|
208
|
-
/// program (`Enum::base`). A base is a non-final heap struct of its fields; each subtype's
|
|
209
|
-
/// struct extends it, so a subtype value already IS a base value and base fields are plain
|
|
210
|
-
/// struct fields.
|
|
211
|
-
pub(crate) fn subtypeBases(source: &ast::Source) -> HashMap<String, Vec<String>> {
|
|
212
|
-
let named: HashSet<&str> = source
|
|
213
|
-
.items
|
|
214
|
-
.iter()
|
|
215
|
-
.filter_map(|i| match i {
|
|
216
|
-
ast::Item::Enum(e) if e.generics.is_empty() && e.isStruct() => Some(e.name.as_str()),
|
|
217
|
-
_ => None,
|
|
218
|
-
})
|
|
219
|
-
.collect();
|
|
220
|
-
let mut out: HashMap<String, Vec<String>> = HashMap::new();
|
|
221
|
-
for item in &source.items {
|
|
222
|
-
let ast::Item::Enum(e) = item else { continue };
|
|
223
|
-
if !e.isStruct() {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if let Some(b) = e.base().filter(|b| named.contains(b)) {
|
|
227
|
-
out.entry(b.to_string()).or_default().push(e.name.clone());
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
out
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/// Ancestors of struct `n` (nearest first) along `parent` links.
|
|
234
|
-
pub(crate) fn subtypeDepth(parent: &HashMap<String, String>, n: &str) -> usize {
|
|
235
|
-
let mut d = 0;
|
|
236
|
-
let mut cur = n;
|
|
237
|
-
while let Some(p) = parent.get(cur) {
|
|
238
|
-
d += 1;
|
|
239
|
-
cur = p;
|
|
240
|
-
if d > parent.len() {
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
d
|
|
245
|
-
}
|
|
246
|
-
|
|
247
207
|
/// Builds the wasm-gc type registry: one struct family per non-generic enum and per
|
|
248
208
|
/// distinct concrete instance (`apps`, e.g. `List[Int]`) of a generic enum, keyed by
|
|
249
209
|
/// the structural `PlumType`; `Array[T]` instances become native wasm arrays of `T`'s
|
|
@@ -278,13 +238,6 @@ pub(crate) fn buildGcTypeRegistry(
|
|
|
278
238
|
leaf_array_idx: HashMap::new(),
|
|
279
239
|
mk_str_fn: None,
|
|
280
240
|
};
|
|
281
|
-
let subtyped = subtypeBases(source);
|
|
282
|
-
let mut parent_of: HashMap<String, String> = HashMap::new();
|
|
283
|
-
for (t, members) in &subtyped {
|
|
284
|
-
for m in members {
|
|
285
|
-
parent_of.insert(m.clone(), t.clone());
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
241
|
let mut enum_decls: HashMap<String, &ast::Enum> = HashMap::new();
|
|
289
242
|
let mut instances: Vec<PlumType> = Vec::new();
|
|
290
243
|
for item in &source.items {
|
|
@@ -305,14 +258,13 @@ pub(crate) fn buildGcTypeRegistry(
|
|
|
305
258
|
}
|
|
306
259
|
}
|
|
307
260
|
}
|
|
308
|
-
// Enum supers, then structs
|
|
261
|
+
// Enum supers first, then structs.
|
|
309
|
-
// a supertype must precede (have a lower index than) every type extending it.
|
|
310
262
|
instances.sort_by_key(|t| {
|
|
311
263
|
let n = plum_checker::nominalName(t).unwrap_or("");
|
|
312
264
|
if enum_decls.get(n).is_some_and(|e| !e.isStruct()) {
|
|
313
|
-
|
|
265
|
+
1
|
|
314
266
|
} else {
|
|
315
|
-
|
|
267
|
+
2
|
|
316
268
|
}
|
|
317
269
|
});
|
|
318
270
|
for ty in &instances {
|
|
@@ -459,16 +411,7 @@ pub(crate) fn buildGcTypeRegistry(
|
|
|
459
411
|
vname
|
|
460
412
|
)
|
|
461
413
|
});
|
|
462
|
-
let super_idx = match ty {
|
|
463
|
-
// A subtype extends its base's class struct.
|
|
464
|
-
PlumType::TNamed(n) if *vname == *n && parent_of.contains_key(n) => {
|
|
465
|
-
let b = &parent_of[n];
|
|
466
|
-
*reg.class_type_idx.get(b).unwrap_or_else(|| {
|
|
467
|
-
panic!("internal codegen error: base struct {b} of {n} not registered before it")
|
|
468
|
-
})
|
|
469
|
-
}
|
|
470
|
-
|
|
414
|
+
let super_idx = reg.superIdx(ty).expect("enum supertype slot");
|
|
471
|
-
};
|
|
472
415
|
let mut field_types: Vec<FieldType> = Vec::new();
|
|
473
416
|
if let Some(leaves) = valueFieldsOf(ty).map(|_| flatValtypes(ty, ®)) {
|
|
474
417
|
// A value enum's box holds its flattened leaves directly.
|
|
@@ -484,10 +427,8 @@ pub(crate) fn buildGcTypeRegistry(
|
|
|
484
427
|
}
|
|
485
428
|
}));
|
|
486
429
|
}
|
|
487
|
-
// A struct some other struct extends is a non-final wasm-gc type.
|
|
488
|
-
let isBase = matches!(ty, PlumType::TNamed(n) if *vname == *n && subtyped.contains_key(n));
|
|
489
430
|
SubType {
|
|
490
|
-
is_final:
|
|
431
|
+
is_final: true,
|
|
491
432
|
supertype_idx: Some(super_idx),
|
|
492
433
|
composite_type: CompositeType {
|
|
493
434
|
inner: CompositeInnerType::Struct(StructType {
|
|
@@ -527,20 +468,6 @@ pub(crate) fn buildGcTypeRegistry(
|
|
|
527
468
|
},
|
|
528
469
|
})
|
|
529
470
|
.collect();
|
|
530
|
-
// A subtype's fields start with its base's (same names, same types): the wasm-gc
|
|
531
|
-
// subtype rule, and what makes a plain `struct.get` on the base type valid.
|
|
532
|
-
for (b, subs) in &subtyped {
|
|
533
|
-
let Some(bf) = enum_params.get(b) else { continue };
|
|
534
|
-
for sname in subs {
|
|
535
|
-
let Some(sv) = enum_variants.get(sname) else { continue };
|
|
536
|
-
let ok = bf.len() <= sv.field_names.len()
|
|
537
|
-
&& bf.iter().zip(sv.field_names.iter().zip(sv.field_types.iter())).all(|((bn, bt), (sn, st))| bn == sn && bt == st);
|
|
538
|
-
assert!(
|
|
539
|
-
ok,
|
|
540
|
-
"internal codegen error: fields of {sname} do not start with the fields of its base {b}"
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
471
|
module.addGcTypes(subtypes);
|
|
545
472
|
reg
|
|
546
473
|
}
|
compiler/wasm-codegen/src/inference.rs
CHANGED
|
@@ -30,13 +30,6 @@ pub(crate) fn fnFieldType(obj_ty: &PlumType, field: &str, enum_variants: &EnumVa
|
|
|
30
30
|
.filter(|t| matches!(t, PlumType::TFun(_, _)))
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/// The strict subtypes of receiver type `obj_ty` overriding `method`, deepest first;
|
|
34
|
-
/// `None` when the call cannot dispatch dynamically.
|
|
35
|
-
pub(crate) fn virtualOverriders(obj_ty: &PlumType, method: &str) -> Option<Vec<String>> {
|
|
36
|
-
let recv = plum_checker::methodReceiverName(obj_ty)?;
|
|
37
|
-
withGen(|s| s.overriders.get(&(recv, method.to_string())).cloned())
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
/// The annotation of `name: Type := value`, which the checker records on `value`.
|
|
41
34
|
pub(crate) fn declaredType(value: &ast::Expr, types: &UnitTypes) -> Option<PlumType> {
|
|
42
35
|
if value.id == ast::NodeId(0) {
|
compiler/wasm-codegen/src/keys.rs
CHANGED
|
@@ -38,12 +38,6 @@ pub(crate) fn fnFieldLocal(call_expr: &ast::Expr) -> String {
|
|
|
38
38
|
format!("fnfield#{}", nodeKey(call_expr))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/// The synthetic local (reserved by `Collector`) parking the receiver of the virtual
|
|
42
|
-
/// call `call_expr` while `ref.test` picks the overrider.
|
|
43
|
-
pub(crate) fn virtRecvLocal(call_expr: &ast::Expr) -> String {
|
|
44
|
-
format!("virt#{}", nodeKey(call_expr))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
41
|
/// Import key of the `plum::crash(Str)` host function in `func_ids`.
|
|
48
42
|
pub(crate) const CRASH_FN_KEY: &str = "__crash";
|
|
49
43
|
|
compiler/wasm-codegen/src/lib.rs
CHANGED
|
@@ -208,7 +208,7 @@ fn compileSourceUnits(
|
|
|
208
208
|
resetGenState(source);
|
|
209
209
|
// The types are keyed by the node ids of the checker's lowered program.
|
|
210
210
|
let source: &ast::Source = lowered.as_deref().unwrap_or(source);
|
|
211
|
-
|
|
211
|
+
checkNodeIds(source);
|
|
212
212
|
let mut setup = Setup::new(source, extra_exports, typed);
|
|
213
213
|
setup.planUnits();
|
|
214
214
|
setup.collectGenericTypes();
|
compiler/wasm-codegen/tests/wasm_codegen/e2e_mem/unique_inplace.rs
CHANGED
|
@@ -38,21 +38,6 @@ fn unique_spread_reuses_object() {
|
|
|
38
38
|
assert_eq!(runInt("reuse_run", &unique), 207);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
#[test]
|
|
42
|
-
fn unique_base_record_spread_in_place() {
|
|
43
|
-
let unique = prog("struct Actor() =\n fun go(self) -> Int = 7\n\nstruct Ship(Actor, x: Int, hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
|
|
44
|
-
assert_eq!(runInt("unique_base_record_spread_in_place", &unique), 700 + 90 + 6);
|
|
45
|
-
assert!(spreadIsUnique("unique_base_record_flag", "hit", &unique));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// A unique spread of a struct with an inherited base field are not checked as user code (F-T6-2).
|
|
49
|
-
#[test]
|
|
50
|
-
fn unique_base_field_record_spread_in_place() {
|
|
51
|
-
let unique = prog("struct Actor(x: Int) =\n fun go(self) -> Int = 7\n\nstruct Ship(Actor(x), hp: Int)\n\nfun hit(s: Ship) -> Ship = Ship(..s, hp: s.hp - 1, x: s.x + 5)\n\nfun main() -> Int =\n s := Ship(x: 1, hp: 10)\n t := hit(s)\n t.go() * 100 + t.hp * 10 + t.x\n");
|
|
52
|
-
assert_eq!(runInt("unique_base_field_record_spread_in_place", &unique), 700 + 90 + 6);
|
|
53
|
-
assert!(spreadIsUnique("unique_base_field_flag", "hit", &unique));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
41
|
/// F-T6-3: `s = Cell(..s, ...)` on a param must reassign the param's own slot, not
|
|
57
42
|
/// a fresh shadow local that every read of `s` (the spread source included) sees unset.
|
|
58
43
|
#[test]
|
compiler/wasm-codegen/tests/wasm_codegen/e2e_records/record_init.rs
CHANGED
|
@@ -5,25 +5,16 @@ fn prog(body: &str) -> String {
|
|
|
5
5
|
format!("{PRE}{body}")
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
const ACTOR: &str = "struct Actor(px: Int, py: Int) =
|
|
9
|
-
fun init(self) =
|
|
10
|
-
self.width: Int = 10
|
|
11
|
-
self.height: Int = self.width * 2
|
|
12
|
-
self.kids: Slice[Int] = Slice[Int]()
|
|
13
|
-
fun area(self) -> Int = self.width * self.height
|
|
14
|
-
|
|
15
|
-
";
|
|
16
|
-
|
|
17
8
|
#[test]
|
|
18
9
|
fn errors_surface_through_the_loader() {
|
|
19
|
-
let src = prog(
|
|
10
|
+
let src = prog("struct Ship(px: Int, py: Int) =
|
|
20
11
|
fun init(self) =
|
|
21
12
|
self.vx = 0
|
|
22
13
|
|
|
23
14
|
fun main() -> Int =
|
|
24
15
|
s := Ship(px: 1, py: 2, width: 3)
|
|
25
16
|
0
|
|
26
|
-
")
|
|
17
|
+
");
|
|
27
18
|
let e = load("err", &src).unwrap_err();
|
|
28
19
|
assert!(e.contains("Ship has no constructor parameter width"), "{e}");
|
|
29
20
|
}
|
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/mod.rs
CHANGED
|
@@ -2,6 +2,5 @@
|
|
|
2
2
|
mod io_entry;
|
|
3
3
|
mod io_primitive;
|
|
4
4
|
mod question;
|
|
5
|
-
mod subtyping;
|
|
6
5
|
mod synthetic_sites;
|
|
7
6
|
mod type_param_static;
|
compiler/wasm-codegen/tests/wasm_codegen/e2e_types/subtyping.rs
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
//! Struct subtyping end to end: a struct embedding another FIRST is its box-free
|
|
2
|
-
//! wasm-gc subtype; `override` dispatches on the concrete type at the call site.
|
|
3
|
-
use plum_test_support::*;
|
|
4
|
-
|
|
5
|
-
fn prog(body: &str) -> String {
|
|
6
|
-
format!("{PRE}{body}")
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/// Compiles with fusion on or off and runs `main() -> Int`.
|
|
10
|
-
fn runWith(name: &str, src: &str, fusion: bool) -> i64 {
|
|
11
|
-
let source = load(name, src).unwrap_or_else(|e| panic!("load failed: {e}"));
|
|
12
|
-
let opts = plum_wasm_codegen::CompileOptions {
|
|
13
|
-
passes: plum_driver::PassOptions { fusion: fusion, explain: false, ..plum_driver::PassOptions::default() },
|
|
14
|
-
..plum_wasm_codegen::CompileOptions::default()
|
|
15
|
-
};
|
|
16
|
-
let bytes = plum_wasm_codegen::compile(&source, &opts)
|
|
17
|
-
.unwrap_or_else(|e| panic!("compile failed: {}", e.iter().map(|d| d.to_string()).collect::<Vec<_>>().join("\n")))
|
|
18
|
-
.wasm;
|
|
19
|
-
wasmparser::validate(&bytes).expect("valid wasm");
|
|
20
|
-
let engine = engine();
|
|
21
|
-
let module = wasmtime::Module::new(&engine, &bytes).unwrap();
|
|
22
|
-
let mut store = wasmtime::Store::new(&engine, ());
|
|
23
|
-
let mut linker = wasmtime::Linker::new(&engine);
|
|
24
|
-
linker.define_unknown_imports_as_default_values(&mut store, &module).unwrap();
|
|
25
|
-
let instance = linker.instantiate(&mut store, &module).unwrap();
|
|
26
|
-
let main = instance.get_typed_func::<(), i64>(&mut store, "main").unwrap();
|
|
27
|
-
main.call(&mut store, ()).unwrap()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
fn both(name: &str, src: &str) -> i64 {
|
|
31
|
-
let a = runWith(name, src, true);
|
|
32
|
-
let b = runWith(name, src, false);
|
|
33
|
-
assert_eq!(a, b, "fusion on/off disagree");
|
|
34
|
-
a
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const ACTORS: &str = "\
|
|
38
|
-
struct Actor(x: Int) =
|
|
39
|
-
fun draw(self) -> Int = 1
|
|
40
|
-
|
|
41
|
-
struct Ship(Actor(x), s: Int) =
|
|
42
|
-
override fun draw(self) -> Int = 2 + self.s
|
|
43
|
-
|
|
44
|
-
struct Rock(Actor(x), r: Int)
|
|
45
|
-
|
|
46
|
-
fun total(xs: Slice[Actor]) -> Int =
|
|
47
|
-
t := 0
|
|
48
|
-
for a := range xs
|
|
49
|
-
t = t + a.draw()
|
|
50
|
-
t
|
|
51
|
-
";
|
|
52
|
-
|
|
53
|
-
#[test]
|
|
54
|
-
fn overrideDispatchOverSlice() {
|
|
55
|
-
let src = prog(&format!(
|
|
56
|
-
"{ACTORS}
|
|
57
|
-
fun main() -> Int =
|
|
58
|
-
xs: Slice[Actor] := Slice[Actor]()
|
|
59
|
-
xs.push(Ship(x: 0, s: 0))
|
|
60
|
-
xs.push(Rock(x: 0, r: 5))
|
|
61
|
-
total(xs)
|
|
62
|
-
"
|
|
63
|
-
));
|
|
64
|
-
// Ship.draw = 2, Rock inherits Actor.draw = 1.
|
|
65
|
-
assert_eq!(both("sub_total", &src), 3);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#[test]
|
|
69
|
-
fn deepestOverrideWins() {
|
|
70
|
-
let src = prog("\
|
|
71
|
-
struct Actor(x: Int) =
|
|
72
|
-
fun draw(self) -> Int = 1
|
|
73
|
-
|
|
74
|
-
struct Ship(Actor(x), s: Int) =
|
|
75
|
-
override fun draw(self) -> Int = 2
|
|
76
|
-
|
|
77
|
-
struct Boss(Ship(x, s), hp: Int)
|
|
78
|
-
|
|
79
|
-
struct Champ(Ship(x, s), w: Int) =
|
|
80
|
-
override fun draw(self) -> Int = 30
|
|
81
|
-
|
|
82
|
-
fun pick(a: Actor) -> Int = a.draw()
|
|
83
|
-
|
|
84
|
-
fun main() -> Int =
|
|
85
|
-
pick(Actor(x: 0)) * 1000 + pick(Ship(x: 0, s: 0)) * 100 + pick(Boss(x: 0, s: 0, hp: 1)) * 10 + pick(Champ(x: 0, s: 0, w: 0)) / 30
|
|
86
|
-
");
|
|
87
|
-
// Actor 1, Ship 2, Boss inherits Ship's 2, Champ 30 -> 1000 + 200 + 20 + 1
|
|
88
|
-
assert_eq!(both("sub_deep", &src), 1221);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#[test]
|
|
92
|
-
fn baseFieldWriteAndReadThroughBase() {
|
|
93
|
-
let src = prog("\
|
|
94
|
-
struct Actor(mut x: Int) =
|
|
95
|
-
fun draw(self) -> Int = self.x
|
|
96
|
-
|
|
97
|
-
struct Ship(Actor(x), s: Int) =
|
|
98
|
-
override fun draw(self) -> Int = self.x * 10 + self.s
|
|
99
|
-
|
|
100
|
-
fun move(a: Actor) =
|
|
101
|
-
a.x = 5
|
|
102
|
-
|
|
103
|
-
fun main() -> Int =
|
|
104
|
-
s := Ship(x: 1, s: 3)
|
|
105
|
-
move(s)
|
|
106
|
-
a: Actor := s
|
|
107
|
-
s.draw() + a.x * 1000
|
|
108
|
-
");
|
|
109
|
-
// s.x became 5 through the Actor view: draw = 53; a.x = 5.
|
|
110
|
-
assert_eq!(both("sub_fields", &src), 53 + 5000);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
#[test]
|
|
114
|
-
fn subtypePatternReadsSubtypeField() {
|
|
115
|
-
let src = prog("\
|
|
116
|
-
struct Actor(x: Int)
|
|
117
|
-
|
|
118
|
-
struct Ship(Actor(x), s: Int)
|
|
119
|
-
|
|
120
|
-
struct Rock(Actor(x), r: Int)
|
|
121
|
-
|
|
122
|
-
fun weight(a: Actor) -> Int =
|
|
123
|
-
match a
|
|
124
|
-
Ship(x, s) => s + x
|
|
125
|
-
Rock(x, r) => r * 100
|
|
126
|
-
_ => 0
|
|
127
|
-
|
|
128
|
-
fun main() -> Int =
|
|
129
|
-
weight(Ship(x: 1, s: 4)) + weight(Rock(x: 0, r: 2)) + weight(Actor(x: 9))
|
|
130
|
-
");
|
|
131
|
-
assert_eq!(both("sub_pattern", &src), 5 + 200 + 0);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#[test]
|
|
135
|
-
fn baseEqualityIsIdentity() {
|
|
136
|
-
let src = prog("\
|
|
137
|
-
struct Actor(x: Int)
|
|
138
|
-
|
|
139
|
-
struct Ship(Actor(x), s: Int)
|
|
140
|
-
|
|
141
|
-
fun main() -> Int =
|
|
142
|
-
a := Ship(x: 1, s: 2)
|
|
143
|
-
b := Ship(x: 1, s: 2)
|
|
144
|
-
c := a
|
|
145
|
-
r := 0
|
|
146
|
-
if a == c
|
|
147
|
-
r = r + 1
|
|
148
|
-
if a == b
|
|
149
|
-
r = r + 10
|
|
150
|
-
r
|
|
151
|
-
");
|
|
152
|
-
assert_eq!(both("sub_eq", &src), 1);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
#[test]
|
|
156
|
-
fn abstractMethodOverriddenByEverySubtype() {
|
|
157
|
-
let src = prog("\
|
|
158
|
-
struct Actor(x: Int) =
|
|
159
|
-
abstract fun draw(self) -> Int
|
|
160
|
-
|
|
161
|
-
struct Ship(Actor(x), s: Int) =
|
|
162
|
-
override fun draw(self) -> Int = 7
|
|
163
|
-
|
|
164
|
-
fun main() -> Int =
|
|
165
|
-
a: Actor := Ship(x: 0, s: 0)
|
|
166
|
-
a.draw()
|
|
167
|
-
");
|
|
168
|
-
assert_eq!(both("sub_abstract", &src), 7);
|
|
169
|
-
}
|
compiler/wasm-codegen/tests/wasm_codegen/fusion_alloc_test.rs
CHANGED
|
@@ -422,8 +422,7 @@ fun main(io: Io) -> Int =
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/// Typing a field receiver must not hide a write: a pure producer reading `c.scale` stays
|
|
425
|
-
/// before a call that writes it, whether the write is the field type's own method or an
|
|
426
|
-
///
|
|
425
|
+
/// before a call that writes it through the field's type.
|
|
427
426
|
#[test]
|
|
428
427
|
fn fusion_keeps_producer_before_write_through_field() {
|
|
429
428
|
let own = format!(
|
|
@@ -458,51 +457,13 @@ fun main(io: Io) -> Int =
|
|
|
458
457
|
let (fused, plain) = (compile(&src, true), compile(&src, false));
|
|
459
458
|
assert_eq!(markTrace(&plain), vec![4, 2]);
|
|
460
459
|
assert_eq!(markTrace(&fused), vec![4, 2]);
|
|
461
|
-
|
|
462
|
-
let overridden = format!(
|
|
463
|
-
"{IO_MARK}struct Base(mut scale: Int) =
|
|
464
|
-
fun poke(self, n: Int) -> Int =
|
|
465
|
-
n
|
|
466
|
-
|
|
467
|
-
struct Sub(Base(scale)) =
|
|
468
|
-
override fun poke(self, n: Int) -> Int =
|
|
469
|
-
self.scale = n
|
|
470
|
-
n
|
|
471
|
-
|
|
472
|
-
struct Holder(base: Base) =
|
|
473
|
-
fun poke(self, n: Int) -> Int =
|
|
474
|
-
self.base.poke(n)
|
|
475
|
-
|
|
476
|
-
fun scaled(c: Base, xs: Lst) -> Lst = match xs
|
|
477
|
-
Nil => Nil
|
|
478
|
-
Cons(h, t) => Cons(head: h * c.scale, tail: scaled(c, t))
|
|
479
|
-
|
|
480
|
-
fun drawL(io: Io, xs: Lst) -> Int = match xs
|
|
481
|
-
Nil => 0
|
|
482
|
-
Cons(h, t) =>
|
|
483
|
-
io.mark(h)
|
|
484
|
-
drawL(io, t)
|
|
485
|
-
|
|
486
|
-
fun go(io: Io, b: Holder, n: Int) -> Int =
|
|
487
|
-
ys := scaled(b.base, build(n))
|
|
488
|
-
_ := b.poke(100)
|
|
489
|
-
drawL(io, ys)
|
|
490
|
-
|
|
491
|
-
fun main(io: Io) -> Int =
|
|
492
|
-
go(io, Holder(base: Sub(scale: 2)), 2)
|
|
493
|
-
"
|
|
494
|
-
);
|
|
495
|
-
let src = loadRaw(&overridden);
|
|
496
|
-
let (fused, plain) = (compile(&src, true), compile(&src, false));
|
|
497
|
-
assert_eq!(markTrace(&plain), vec![4, 2]);
|
|
498
|
-
assert_eq!(markTrace(&fused), vec![4, 2]);
|
|
499
460
|
}
|
|
500
461
|
|
|
501
462
|
// ---------- override dispatch: fusion only changes speed ----------
|
|
502
463
|
|
|
503
464
|
/// `scaled` reads `c.scale`, `drawL` marks the result: a producer moved past a write marks
|
|
504
465
|
/// the new scale.
|
|
505
|
-
const
|
|
466
|
+
const FUSION_PRELUDE: &str = "fun scaled(c: Base, xs: Lst) -> Lst = match xs
|
|
506
467
|
Nil => Nil
|
|
507
468
|
Cons(h, t) => Cons(head: h * c.scale, tail: scaled(c, t))
|
|
508
469
|
|
|
@@ -517,322 +478,16 @@ fun sumL(xs: Lst) -> Int = match xs
|
|
|
517
478
|
Cons(h, t) => h + sumL(t)
|
|
518
479
|
";
|
|
519
480
|
|
|
520
|
-
/// Base's `poke` only reads, the override writes.
|
|
521
|
-
const SUB_WRITES: &str = "struct Base(mut scale: Int) =
|
|
522
|
-
fun poke(self, n: Int) -> Int =
|
|
523
|
-
n
|
|
524
|
-
|
|
525
|
-
struct Sub(Base(scale)) =
|
|
526
|
-
override fun poke(self, n: Int) -> Int =
|
|
527
|
-
self.scale = n
|
|
528
|
-
n
|
|
529
|
-
";
|
|
530
|
-
|
|
531
|
-
/// Base's `poke` writes, the override only reads.
|
|
532
|
-
const BASE_WRITES: &str = "struct Base(mut scale: Int) =
|
|
533
|
-
fun poke(self, n: Int) -> Int =
|
|
534
|
-
self.scale = n
|
|
535
|
-
n
|
|
536
|
-
|
|
537
|
-
struct Sub(Base(scale)) =
|
|
538
|
-
override fun poke(self, n: Int) -> Int =
|
|
539
|
-
n
|
|
540
|
-
";
|
|
541
|
-
|
|
542
|
-
/// Base's `poke` reads, `Mid`'s reads, `Deep`'s writes.
|
|
543
|
-
const DEEP_WRITES: &str = "struct Base(mut scale: Int) =
|
|
544
|
-
fun poke(self, n: Int) -> Int =
|
|
545
|
-
n
|
|
546
|
-
|
|
547
|
-
struct Mid(Base(scale)) =
|
|
548
|
-
override fun poke(self, n: Int) -> Int =
|
|
549
|
-
n + 1
|
|
550
|
-
|
|
551
|
-
struct Deep(Mid(scale)) =
|
|
552
|
-
override fun poke(self, n: Int) -> Int =
|
|
553
|
-
self.scale = n
|
|
554
|
-
n
|
|
555
|
-
";
|
|
556
|
-
|
|
557
|
-
/// Base's `poke` reads, `Mid`'s writes, the leaf `Deep`'s reads.
|
|
558
|
-
const MID_WRITES: &str = "struct Base(mut scale: Int) =
|
|
559
|
-
fun poke(self, n: Int) -> Int =
|
|
560
|
-
n
|
|
561
|
-
|
|
562
|
-
struct Mid(Base(scale)) =
|
|
563
|
-
override fun poke(self, n: Int) -> Int =
|
|
564
|
-
self.scale = n
|
|
565
|
-
n
|
|
566
|
-
|
|
567
|
-
struct Deep(Mid(scale)) =
|
|
568
|
-
override fun poke(self, n: Int) -> Int =
|
|
569
|
-
n
|
|
570
|
-
";
|
|
571
|
-
|
|
572
481
|
/// The marks of `main` fused and unfused, checked against `want`.
|
|
573
482
|
#[track_caller]
|
|
574
483
|
fn sameTrace(types: &str, rest: &str, want: &[i32]) {
|
|
575
|
-
let text = format!("{IO_MARK}{types}{
|
|
484
|
+
let text = format!("{IO_MARK}{types}{FUSION_PRELUDE}{rest}");
|
|
576
485
|
let src = loadRaw(&text);
|
|
577
486
|
let (fused, plain) = (compile(&src, true), compile(&src, false));
|
|
578
487
|
assert_eq!(markTrace(&plain), want, "--no-fusion:\n{rest}");
|
|
579
488
|
assert_eq!(markTrace(&fused), want, "fused:\n{rest}");
|
|
580
489
|
}
|
|
581
490
|
|
|
582
|
-
/// `go` moves a producer past `{call}` only if the call cannot write `b.scale`.
|
|
583
|
-
fn goWith(call: &str, param: &str, value: &str) -> String {
|
|
584
|
-
format!(
|
|
585
|
-
"fun go(io: Io, b: {param}, n: Int) -> Int =
|
|
586
|
-
ys := scaled(b, build(n))
|
|
587
|
-
{call}
|
|
588
|
-
drawL(io, ys)
|
|
589
|
-
|
|
590
|
-
fun main(io: Io) -> Int =
|
|
591
|
-
go(io, {value}, 2)
|
|
592
|
-
"
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/// A helper calling an overridden method through a typed parameter is as read-only as the
|
|
597
|
-
/// union of the method and every override: the override here writes.
|
|
598
|
-
#[test]
|
|
599
|
-
fn fusion_override_through_typed_param() {
|
|
600
|
-
let helper = "fun pk(b: Base) -> Int = b.poke(100)\n";
|
|
601
|
-
let rest = format!("{helper}{}", goWith("_ := pk(b)", "Base", "Sub(scale: 2)"));
|
|
602
|
-
sameTrace(SUB_WRITES, &rest, &[4, 2]);
|
|
603
|
-
// called more than once, the result used
|
|
604
|
-
let rest = format!(
|
|
605
|
-
"{helper}fun go(io: Io, b: Base, n: Int) -> Int =
|
|
606
|
-
ys := scaled(b, build(n))
|
|
607
|
-
a := pk(b) + pk(b)
|
|
608
|
-
io.mark(a)
|
|
609
|
-
drawL(io, ys)
|
|
610
|
-
|
|
611
|
-
fun main(io: Io) -> Int =
|
|
612
|
-
go(io, Sub(scale: 2), 2)
|
|
613
|
-
"
|
|
614
|
-
);
|
|
615
|
-
sameTrace(SUB_WRITES, &rest, &[200, 4, 2]);
|
|
616
|
-
// the base writes, the override does not; the base runs
|
|
617
|
-
let rest = format!("{helper}{}", goWith("_ := pk(b)", "Base", "Base(scale: 2)"));
|
|
618
|
-
sameTrace(BASE_WRITES, &rest, &[4, 2]);
|
|
619
|
-
// called directly: `b`'s method is the base one, not only the overrides
|
|
620
|
-
sameTrace(SUB_WRITES, &goWith("_ := b.poke(100)", "Base", "Sub(scale: 2)"), &[4, 2]);
|
|
621
|
-
sameTrace(BASE_WRITES, &goWith("_ := b.poke(100)", "Base", "Base(scale: 2)"), &[4, 2]);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/// The override writes through a field, directly or through that field's method.
|
|
625
|
-
#[test]
|
|
626
|
-
fn fusion_override_writes_through_field() {
|
|
627
|
-
let types = |write: &str| {
|
|
628
|
-
format!(
|
|
629
|
-
"struct Cell(mut v: Int) =
|
|
630
|
-
fun set(self, n: Int) =
|
|
631
|
-
self.v = n
|
|
632
|
-
|
|
633
|
-
struct Base(cell: Cell, mut scale: Int) =
|
|
634
|
-
fun poke(self, n: Int) -> Int =
|
|
635
|
-
n
|
|
636
|
-
|
|
637
|
-
struct Sub(Base(cell, scale)) =
|
|
638
|
-
override fun poke(self, n: Int) -> Int =
|
|
639
|
-
{write}
|
|
640
|
-
n
|
|
641
|
-
|
|
642
|
-
fun cells(c: Cell, xs: Lst) -> Lst = match xs
|
|
643
|
-
Nil => Nil
|
|
644
|
-
Cons(h, t) => Cons(head: h * c.v, tail: cells(c, t))
|
|
645
|
-
"
|
|
646
|
-
)
|
|
647
|
-
};
|
|
648
|
-
let rest = "fun pk(b: Base) -> Int = b.poke(100)
|
|
649
|
-
|
|
650
|
-
fun go(io: Io, b: Base, n: Int) -> Int =
|
|
651
|
-
ys := cells(b.cell, build(n))
|
|
652
|
-
_ := pk(b)
|
|
653
|
-
drawL(io, ys)
|
|
654
|
-
|
|
655
|
-
fun main(io: Io) -> Int =
|
|
656
|
-
go(io, Sub(cell: Cell(v: 2), scale: 1), 2)
|
|
657
|
-
";
|
|
658
|
-
sameTrace(&types("self.cell.v = n"), rest, &[4, 2]);
|
|
659
|
-
sameTrace(&types("self.cell.set(n)"), rest, &[4, 2]);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/// Two levels of override: the call on `Base` or `Mid` is the union of all of them below,
|
|
663
|
-
/// whichever level writes; an inherited method (`Mid` declares none) counts the same.
|
|
664
|
-
#[test]
|
|
665
|
-
fn fusion_override_two_levels() {
|
|
666
|
-
let helper = |t: &str| format!("fun pk(b: {t}) -> Int = b.poke(100)\n");
|
|
667
|
-
for (types, t, value) in [
|
|
668
|
-
(DEEP_WRITES, "Base", "Deep(scale: 2)"),
|
|
669
|
-
(DEEP_WRITES, "Mid", "Deep(scale: 2)"),
|
|
670
|
-
(MID_WRITES, "Base", "Mid(scale: 2)"),
|
|
671
|
-
(MID_WRITES, "Mid", "Mid(scale: 2)"),
|
|
672
|
-
] {
|
|
673
|
-
let rest = format!("{}{}", helper(t), goWith("_ := pk(b)", t, value));
|
|
674
|
-
sameTrace(types, &rest, &[4, 2]);
|
|
675
|
-
sameTrace(types, &goWith("_ := b.poke(100)", t, value), &[4, 2]);
|
|
676
|
-
}
|
|
677
|
-
let inherited = "struct Base(mut scale: Int) =
|
|
678
|
-
fun poke(self, n: Int) -> Int =
|
|
679
|
-
n
|
|
680
|
-
|
|
681
|
-
struct Mid(Base(scale))
|
|
682
|
-
|
|
683
|
-
struct Deep(Mid(scale)) =
|
|
684
|
-
override fun poke(self, n: Int) -> Int =
|
|
685
|
-
self.scale = n
|
|
686
|
-
n
|
|
687
|
-
";
|
|
688
|
-
let rest = format!("{}{}", helper("Mid"), goWith("_ := pk(b)", "Mid", "Deep(scale: 2)"));
|
|
689
|
-
sameTrace(inherited, &rest, &[4, 2]);
|
|
690
|
-
sameTrace(inherited, &goWith("_ := b.poke(100)", "Mid", "Deep(scale: 2)"), &[4, 2]);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/// Through a field of the base type, in a helper or at the site; through a local holding it.
|
|
694
|
-
#[test]
|
|
695
|
-
fn fusion_override_through_field_and_local() {
|
|
696
|
-
for body in ["h.base.poke(100)", "pk(h)", "viaLocal(h)", "viaTyped(h)"] {
|
|
697
|
-
let rest = format!(
|
|
698
|
-
"struct Holder(base: Base)
|
|
699
|
-
|
|
700
|
-
fun pk(h: Holder) -> Int = h.base.poke(100)
|
|
701
|
-
|
|
702
|
-
fun viaLocal(h: Holder) -> Int =
|
|
703
|
-
l := h.base
|
|
704
|
-
l.poke(100)
|
|
705
|
-
|
|
706
|
-
fun viaTyped(h: Holder) -> Int =
|
|
707
|
-
l: Base := h.base
|
|
708
|
-
l.poke(100)
|
|
709
|
-
|
|
710
|
-
fun go(io: Io, h: Holder, n: Int) -> Int =
|
|
711
|
-
ys := scaled(h.base, build(n))
|
|
712
|
-
_ := {body}
|
|
713
|
-
drawL(io, ys)
|
|
714
|
-
|
|
715
|
-
fun main(io: Io) -> Int =
|
|
716
|
-
go(io, Holder(base: VALUE), 2)
|
|
717
|
-
"
|
|
718
|
-
);
|
|
719
|
-
sameTrace(SUB_WRITES, &rest.replace("VALUE", "Sub(scale: 2)"), &[4, 2]);
|
|
720
|
-
sameTrace(BASE_WRITES, &rest.replace("VALUE", "Base(scale: 2)"), &[4, 2]);
|
|
721
|
-
sameTrace(MID_WRITES, &rest.replace("VALUE", "Mid(scale: 2)"), &[4, 2]);
|
|
722
|
-
}
|
|
723
|
-
// a local at the site
|
|
724
|
-
let rest = "struct Holder(base: Base)
|
|
725
|
-
|
|
726
|
-
fun go(io: Io, h: Holder, n: Int) -> Int =
|
|
727
|
-
ys := scaled(h.base, build(n))
|
|
728
|
-
l := h.base
|
|
729
|
-
_ := l.poke(100)
|
|
730
|
-
drawL(io, ys)
|
|
731
|
-
|
|
732
|
-
fun main(io: Io) -> Int =
|
|
733
|
-
go(io, Holder(base: VALUE), 2)
|
|
734
|
-
";
|
|
735
|
-
sameTrace(SUB_WRITES, &rest.replace("VALUE", "Sub(scale: 2)"), &[4, 2]);
|
|
736
|
-
sameTrace(BASE_WRITES, &rest.replace("VALUE", "Base(scale: 2)"), &[4, 2]);
|
|
737
|
-
sameTrace(MID_WRITES, &rest.replace("VALUE", "Mid(scale: 2)"), &[4, 2]);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
/// A method calling `self.poke`: `self` may be a subtype, so a base method that only calls
|
|
741
|
-
/// an overridden one is as read-only as the override.
|
|
742
|
-
#[test]
|
|
743
|
-
fn fusion_override_through_self() {
|
|
744
|
-
let types = "struct Base(mut scale: Int) =
|
|
745
|
-
fun poke(self, n: Int) -> Int =
|
|
746
|
-
n
|
|
747
|
-
fun run(self, n: Int) -> Int =
|
|
748
|
-
self.poke(n)
|
|
749
|
-
|
|
750
|
-
struct Sub(Base(scale)) =
|
|
751
|
-
override fun poke(self, n: Int) -> Int =
|
|
752
|
-
self.scale = n
|
|
753
|
-
n
|
|
754
|
-
";
|
|
755
|
-
sameTrace(types, &goWith("_ := b.run(100)", "Base", "Sub(scale: 2)"), &[4, 2]);
|
|
756
|
-
let rest = format!("fun pk(b: Base) -> Int = b.run(100)\n{}", goWith("_ := pk(b)", "Base", "Sub(scale: 2)"));
|
|
757
|
-
sameTrace(types, &rest, &[4, 2]);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/// A value read through an overridden method is pure only if the method and all its
|
|
761
|
-
/// overrides are: the base one writes, so `b.next()` must not move past `io.mark(b.scale)`.
|
|
762
|
-
#[test]
|
|
763
|
-
fn fusion_override_keeps_impure_argument_in_place() {
|
|
764
|
-
let types = "struct Base(mut scale: Int) =
|
|
765
|
-
fun next(self) -> Int =
|
|
766
|
-
self.scale = self.scale + 1
|
|
767
|
-
self.scale
|
|
768
|
-
|
|
769
|
-
struct Sub(Base(scale)) =
|
|
770
|
-
override fun next(self) -> Int =
|
|
771
|
-
self.scale
|
|
772
|
-
|
|
773
|
-
fun times(k: Int, xs: Lst) -> Lst = match xs
|
|
774
|
-
Nil => Nil
|
|
775
|
-
Cons(h, t) => Cons(head: h * k, tail: times(k, t))
|
|
776
|
-
";
|
|
777
|
-
let rest = "fun go(io: Io, b: Base, n: Int) -> Int =
|
|
778
|
-
ys := times(b.next(), build(n))
|
|
779
|
-
io.mark(b.scale)
|
|
780
|
-
drawL(io, ys)
|
|
781
|
-
|
|
782
|
-
fun main(io: Io) -> Int =
|
|
783
|
-
go(io, VALUE, 2)
|
|
784
|
-
";
|
|
785
|
-
sameTrace(types, &rest.replace("VALUE", "Base(scale: 2)"), &[3, 6, 3]);
|
|
786
|
-
sameTrace(types, &rest.replace("VALUE", "Sub(scale: 2)"), &[2, 4, 2]);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/// A wrapper method is unfolded at its call only when it is the one method the call can
|
|
790
|
-
/// run: never past an override, whichever side of it the receiver is.
|
|
791
|
-
#[test]
|
|
792
|
-
fn fusion_never_unfolds_past_override() {
|
|
793
|
-
let types = "struct Base(mut scale: Int) =
|
|
794
|
-
fun total(self, xs: Lst) -> Int = sumL(xs)
|
|
795
|
-
|
|
796
|
-
struct Sub(Base(scale)) =
|
|
797
|
-
override fun total(self, xs: Lst) -> Int = sumL(xs) + 1000
|
|
798
|
-
|
|
799
|
-
struct Mid2(Base(scale)) =
|
|
800
|
-
override fun total(self, xs: Lst) -> Int = sumL(xs) + 2000
|
|
801
|
-
|
|
802
|
-
struct Deep(Mid2(scale)) =
|
|
803
|
-
override fun total(self, xs: Lst) -> Int = sumL(xs) + 3000
|
|
804
|
-
|
|
805
|
-
struct Holder(base: Base)
|
|
806
|
-
";
|
|
807
|
-
for (call, param, arg) in [("b.total(mapL(build(2)))", "Base", "VALUE"), ("b.base.total(mapL(build(2)))", "Holder", "Holder(base: VALUE)")] {
|
|
808
|
-
for (value, want) in [("Base(scale: 1)", 30), ("Sub(scale: 1)", 1030), ("Mid2(scale: 1)", 2030), ("Deep(scale: 1)", 3030)] {
|
|
809
|
-
let rest = format!(
|
|
810
|
-
"fun go(io: Io, b: {param}) -> Int =
|
|
811
|
-
io.mark({call})
|
|
812
|
-
0
|
|
813
|
-
|
|
814
|
-
fun main(io: Io) -> Int =
|
|
815
|
-
go(io, {})
|
|
816
|
-
",
|
|
817
|
-
arg.replace("VALUE", value)
|
|
818
|
-
);
|
|
819
|
-
sameTrace(types, &rest, &[want]);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
// through a local, whose type the analyses do not know
|
|
823
|
-
let rest = "fun go(io: Io, h: Holder) -> Int =
|
|
824
|
-
l := h.base
|
|
825
|
-
io.mark(l.total(mapL(build(2))))
|
|
826
|
-
0
|
|
827
|
-
|
|
828
|
-
fun main(io: Io) -> Int =
|
|
829
|
-
go(io, Holder(base: VALUE))
|
|
830
|
-
";
|
|
831
|
-
for (value, want) in [("Base(scale: 1)", 30), ("Sub(scale: 1)", 1030), ("Deep(scale: 1)", 3030)] {
|
|
832
|
-
sameTrace(types, &rest.replace("VALUE", value), &[want]);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
491
|
/// Unknown receiver, unrelated types: the first method of the name is not the one to unfold.
|
|
837
492
|
#[test]
|
|
838
493
|
fn fusion_never_unfolds_a_guessed_method() {
|
|
@@ -895,90 +550,6 @@ fun main(io: Io) -> Int =
|
|
|
895
550
|
sameTrace(types, rest, &[4, 2]);
|
|
896
551
|
}
|
|
897
552
|
|
|
898
|
-
/// A method matching on a parameter, handed a just-built value: its arm is picked at the
|
|
899
|
-
/// call only if it is the method the call runs. By name, the leaf override is a guess.
|
|
900
|
-
#[test]
|
|
901
|
-
fn fusion_never_specialises_a_guessed_method() {
|
|
902
|
-
let types = "struct Base(mut scale: Int) =
|
|
903
|
-
fun first(self, xs: Lst) -> Int = match xs
|
|
904
|
-
Nil => 0
|
|
905
|
-
Cons(h, t) => h
|
|
906
|
-
|
|
907
|
-
struct Sub(Base(scale)) =
|
|
908
|
-
override fun first(self, xs: Lst) -> Int = match xs
|
|
909
|
-
Nil => 1000
|
|
910
|
-
Cons(h, t) => h + 1000
|
|
911
|
-
|
|
912
|
-
struct Holder(base: Base)
|
|
913
|
-
";
|
|
914
|
-
let rest = "fun go(io: Io, h: Holder) -> Int =
|
|
915
|
-
l := h.base
|
|
916
|
-
io.mark(l.first(Nil))
|
|
917
|
-
io.mark(l.first(Cons(head: 7, tail: Nil)))
|
|
918
|
-
0
|
|
919
|
-
|
|
920
|
-
fun main(io: Io) -> Int =
|
|
921
|
-
go(io, Holder(base: VALUE))
|
|
922
|
-
";
|
|
923
|
-
sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[0, 7]);
|
|
924
|
-
sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1000, 1007]);
|
|
925
|
-
// a Unit method whose picked arm has several statements, spliced at the call
|
|
926
|
-
let types = "struct Base(mut scale: Int) =
|
|
927
|
-
fun show(self, io: Io, xs: Lst) = match xs
|
|
928
|
-
Nil =>
|
|
929
|
-
io.mark(1)
|
|
930
|
-
io.mark(2)
|
|
931
|
-
Cons(h, t) =>
|
|
932
|
-
io.mark(h)
|
|
933
|
-
|
|
934
|
-
struct Sub(Base(scale)) =
|
|
935
|
-
override fun show(self, io: Io, xs: Lst) = match xs
|
|
936
|
-
Nil =>
|
|
937
|
-
io.mark(1001)
|
|
938
|
-
io.mark(1002)
|
|
939
|
-
Cons(h, t) =>
|
|
940
|
-
io.mark(h)
|
|
941
|
-
|
|
942
|
-
struct Holder(base: Base)
|
|
943
|
-
";
|
|
944
|
-
let rest = "fun go(io: Io, h: Holder) -> Int =
|
|
945
|
-
l := h.base
|
|
946
|
-
l.show(io, Nil)
|
|
947
|
-
0
|
|
948
|
-
|
|
949
|
-
fun main(io: Io) -> Int =
|
|
950
|
-
go(io, Holder(base: VALUE))
|
|
951
|
-
";
|
|
952
|
-
sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[1, 2]);
|
|
953
|
-
sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1001, 1002]);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/// A producer behind an overridden method is not fused by guessing the leaf override.
|
|
957
|
-
#[test]
|
|
958
|
-
fn fusion_never_fuses_a_guessed_producer() {
|
|
959
|
-
let types = "struct Base(mut scale: Int) =
|
|
960
|
-
fun items(self) -> Lst = Cons(head: 1, tail: Nil)
|
|
961
|
-
|
|
962
|
-
struct Sub(Base(scale)) =
|
|
963
|
-
override fun items(self) -> Lst = Cons(head: 1000, tail: Nil)
|
|
964
|
-
|
|
965
|
-
struct Holder(base: Base)
|
|
966
|
-
";
|
|
967
|
-
let rest = "fun go(io: Io, h: Holder) -> Int =
|
|
968
|
-
l := h.base
|
|
969
|
-
io.mark(sumL(l.items()))
|
|
970
|
-
io.mark(sumL(h.base.items()))
|
|
971
|
-
b := h.base
|
|
972
|
-
io.mark(drawL(io, b.items()))
|
|
973
|
-
0
|
|
974
|
-
|
|
975
|
-
fun main(io: Io) -> Int =
|
|
976
|
-
go(io, Holder(base: VALUE))
|
|
977
|
-
";
|
|
978
|
-
sameTrace(types, &rest.replace("VALUE", "Base(scale: 0)"), &[1, 1, 1, 0]);
|
|
979
|
-
sameTrace(types, &rest.replace("VALUE", "Sub(scale: 0)"), &[1000, 1000, 1000, 0]);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
553
|
/// A method call on a parameter shadowed at the site, by a pattern name on a value of unknown
|
|
983
554
|
/// type or a closure parameter: the shadowing name has its own type, whose `poke` writes.
|
|
984
555
|
#[test]
|
docs/language.md
CHANGED
|
@@ -16,7 +16,6 @@ A tour of the language, one idea at a time. Every feature also has a runnable fi
|
|
|
16
16
|
- [Generics](#generics)
|
|
17
17
|
- [Methods, statics and constants](#methods-statics-and-constants)
|
|
18
18
|
- [Operators on your own types](#operators-on-your-own-types)
|
|
19
|
-
- [Inheritance](#inheritance)
|
|
20
19
|
- [Modules](#modules)
|
|
21
20
|
- [Tests](#tests)
|
|
22
21
|
- [Talking to the outside world](#talking-to-the-outside-world)
|
|
@@ -441,37 +440,6 @@ struct Vec2(x: Float, y: Float) =
|
|
|
441
440
|
Vec2(x: self.x * s, y: self.y * s)
|
|
442
441
|
```
|
|
443
442
|
|
|
444
|
-
## Inheritance
|
|
445
|
-
|
|
446
|
-
A struct can embed one other struct by listing it first. It gets the base's fields and methods, and can be used anywhere the base is expected.
|
|
447
|
-
|
|
448
|
-
```plum
|
|
449
|
-
import std/Io
|
|
450
|
-
import std/Int
|
|
451
|
-
import std/Str
|
|
452
|
-
|
|
453
|
-
struct Animal(name: Str) =
|
|
454
|
-
fun speak(self, io: Io) =
|
|
455
|
-
io.printLn("...")
|
|
456
|
-
|
|
457
|
-
struct Dog(Animal(name), tricks: Int) =
|
|
458
|
-
override fun speak(self, io: Io) =
|
|
459
|
-
io.printLn("Woof")
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
The header names which base fields callers pass. `override fun` replaces a base method, and the right version is picked at run time. `self.Animal.speak(io)` calls the base's version.
|
|
463
|
-
|
|
464
|
-
`abstract fun` declares a method with no body. Every subtype must override it, and the base can't be constructed:
|
|
465
|
-
|
|
466
|
-
```plum
|
|
467
|
-
struct Shape() =
|
|
468
|
-
abstract fun area(self) -> Float
|
|
469
|
-
|
|
470
|
-
struct Square(Shape, side: Float) =
|
|
471
|
-
override fun area(self) -> Float =
|
|
472
|
-
self.side * self.side
|
|
473
|
-
```
|
|
474
|
-
|
|
475
443
|
## Modules
|
|
476
444
|
|
|
477
445
|
A file starts with its imports. Nothing is implicit, not even `Int` or `Str` methods, and each file imports what it uses.
|
examples/structs_records.plum
CHANGED
|
@@ -128,7 +128,7 @@ struct Bag(n: Int) =
|
|
|
128
128
|
self.items: Slice[Int] = Slice[Int]()
|
|
129
129
|
self.cap: Int = self.n * 2
|
|
130
130
|
|
|
131
|
-
# ----
|
|
131
|
+
# ---- a struct holding another struct ----
|
|
132
132
|
|
|
133
133
|
struct Counter(mut count: Int) =
|
|
134
134
|
|
|
@@ -141,13 +141,9 @@ struct Counter(mut count: Int) =
|
|
|
141
141
|
fun show(self) -> Str =
|
|
142
142
|
"counter {self.count}"
|
|
143
143
|
|
|
144
|
-
struct Scoreboard(Counter
|
|
144
|
+
struct Scoreboard(counter: Counter, name: Str) =
|
|
145
|
-
|
|
146
|
-
fun init(self) =
|
|
147
|
-
self.best: Int = 0
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
fun show(self) -> Str =
|
|
150
|
-
"{self.name}: {self.
|
|
146
|
+
"{self.name}: {self.counter.show()}"
|
|
151
147
|
|
|
152
148
|
# ==== digit names ====
|
|
153
149
|
|
|
@@ -368,16 +364,12 @@ test "init lists are fresh per construction"
|
|
|
368
364
|
assert a.items.length() == 2
|
|
369
365
|
assert b.items.length() == 0
|
|
370
366
|
|
|
371
|
-
test "a
|
|
367
|
+
test "a struct holds another and calls its methods"
|
|
372
|
-
s := Scoreboard(count: 5, name: "hi")
|
|
368
|
+
s := Scoreboard(counter: Counter(count: 5), name: "hi")
|
|
373
|
-
assert s.count == 5
|
|
369
|
+
assert s.counter.count == 5
|
|
374
|
-
s.bump()
|
|
370
|
+
s.counter.bump()
|
|
375
|
-
assert s.count == 6
|
|
371
|
+
assert s.counter.count == 6
|
|
376
|
-
assert s.best == 0
|
|
377
|
-
|
|
378
|
-
test "override replaces an embedded method and self.X.m() calls the original"
|
|
379
|
-
s := Scoreboard(count: 1, name: "hi")
|
|
380
|
-
assert s.show() == "hi: counter
|
|
372
|
+
assert s.show() == "hi: counter 6"
|
|
381
373
|
|
|
382
374
|
# ==== digit names tests ====
|
|
383
375
|
|
examples/subtyping.plum
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# Subtyping: a struct that embeds another struct FIRST is its subtype. It gains
|
|
2
|
-
# the base's fields and methods, is accepted wherever the base is expected, and
|
|
3
|
-
# `override` picks the subtype's method at runtime. A base method without a body
|
|
4
|
-
# is abstract: every subtype must override it, and the base itself can't be built.
|
|
5
|
-
import std/Str
|
|
6
|
-
import std/Int
|
|
7
|
-
import std/Float
|
|
8
|
-
import std/Slice
|
|
9
|
-
|
|
10
|
-
struct Vec2(x: Int, y: Int) =
|
|
11
|
-
fun add(self, o: Vec2) -> Vec2 = Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
12
|
-
|
|
13
|
-
# ==== a base with defaults ====
|
|
14
|
-
|
|
15
|
-
struct Entity(mut pos: Vec2) =
|
|
16
|
-
fun draw(self) -> Int = 1
|
|
17
|
-
fun moveBy(self, d: Vec2) =
|
|
18
|
-
self.pos = self.pos + d
|
|
19
|
-
fun drawTwice(self) -> Int = self.draw() * 2
|
|
20
|
-
|
|
21
|
-
# Ship overrides `draw`; Rock keeps the default.
|
|
22
|
-
struct Ship(Entity(pos), speed: Int) =
|
|
23
|
-
override fun draw(self) -> Int = 100 + self.speed
|
|
24
|
-
|
|
25
|
-
struct Rock(Entity(pos), weight: Int)
|
|
26
|
-
|
|
27
|
-
# Takes the base; the call dispatches on the concrete type.
|
|
28
|
-
fun drawIt(e: Entity) -> Int = e.draw()
|
|
29
|
-
|
|
30
|
-
fun total(es: Slice[Entity]) -> Int =
|
|
31
|
-
t := 0
|
|
32
|
-
for e := range es
|
|
33
|
-
t = t + e.draw()
|
|
34
|
-
t
|
|
35
|
-
|
|
36
|
-
# ==== an abstract method ====
|
|
37
|
-
|
|
38
|
-
# `area` has no body: Shape can't be constructed and each subtype overrides it.
|
|
39
|
-
struct Shape(name: Int) =
|
|
40
|
-
abstract fun area(self) -> Int
|
|
41
|
-
fun doubled(self) -> Int = self.area() * 2
|
|
42
|
-
|
|
43
|
-
struct Square(Shape(name), side: Int) =
|
|
44
|
-
override fun area(self) -> Int = self.side * self.side
|
|
45
|
-
|
|
46
|
-
struct Rect(Shape(name), w: Int, h: Int) =
|
|
47
|
-
override fun area(self) -> Int = self.w * self.h
|
|
48
|
-
|
|
49
|
-
fun areaOf(s: Shape) -> Int = s.area()
|
|
50
|
-
|
|
51
|
-
fun speedOf(e: Entity) -> Int =
|
|
52
|
-
match e
|
|
53
|
-
Ship(_, speed) => speed
|
|
54
|
-
_ => -1
|
|
55
|
-
|
|
56
|
-
# ==== tests ====
|
|
57
|
-
|
|
58
|
-
test "ship passed as the base calls its override"
|
|
59
|
-
ship := Ship(pos: Vec2(x: 1, y: 2), speed: 5)
|
|
60
|
-
assert drawIt(ship) == 105
|
|
61
|
-
|
|
62
|
-
test "a subtype without an override uses the base method"
|
|
63
|
-
assert drawIt(Rock(pos: Vec2(x: 0, y: 0), weight: 3)) == 1
|
|
64
|
-
|
|
65
|
-
test "a slice of the base dispatches per element"
|
|
66
|
-
es := Slice[Entity]()
|
|
67
|
-
es.push(Ship(pos: Vec2(x: 0, y: 0), speed: 1))
|
|
68
|
-
es.push(Rock(pos: Vec2(x: 0, y: 0), weight: 9))
|
|
69
|
-
assert total(es) == 102
|
|
70
|
-
|
|
71
|
-
test "a base default calls the subtype override"
|
|
72
|
-
e: Entity := Ship(pos: Vec2(x: 0, y: 0), speed: 0)
|
|
73
|
-
assert e.drawTwice() == 200
|
|
74
|
-
|
|
75
|
-
test "base fields are shared with the subtype"
|
|
76
|
-
ship := Ship(pos: Vec2(x: 1, y: 2), speed: 5)
|
|
77
|
-
e: Entity := ship
|
|
78
|
-
e.moveBy(Vec2(x: 10, y: 20))
|
|
79
|
-
assert ship.pos.x == 11
|
|
80
|
-
assert e.pos.y == 22
|
|
81
|
-
|
|
82
|
-
test "match on a base value with a subtype pattern"
|
|
83
|
-
assert speedOf(Ship(pos: Vec2(x: 0, y: 0), speed: 4)) == 4
|
|
84
|
-
assert speedOf(Rock(pos: Vec2(x: 0, y: 0), weight: 1)) == -1
|
|
85
|
-
|
|
86
|
-
test "abstract methods dispatch to each subtype"
|
|
87
|
-
assert areaOf(Square(name: 1, side: 3)) == 9
|
|
88
|
-
assert areaOf(Rect(name: 2, w: 2, h: 5)) == 10
|
|
89
|
-
s: Shape := Rect(name: 2, w: 2, h: 5)
|
|
90
|
-
assert s.doubled() == 20
|
|
91
|
-
|
|
92
|
-
test "spread copies a subtype"
|
|
93
|
-
a := Ship(pos: Vec2(x: 1, y: 1), speed: 5)
|
|
94
|
-
b := Ship(..a, speed: 6)
|
|
95
|
-
assert b.speed == 6
|
|
96
|
-
assert b.pos.x == 1
|
|
97
|
-
assert a.speed == 5
|
examples/unions.plum
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Inline-variant enums: each variant declares its own fields, `enum Shape = Circle(r: Float) | Rect(w: Float, h: Float)`.
|
|
2
2
|
# A variant is not a type: build it by name, read its fields by pattern. Wrap a
|
|
3
3
|
# primitive in a one-field variant (`JInt(v: Int)`). An enum can define methods
|
|
4
|
-
# (a `match self` body),
|
|
4
|
+
# (a `match self` body), which is how one call runs different code per variant.
|
|
5
|
-
# with `override` dispatch (the replacement for auto-dispatch over members).
|
|
6
5
|
module unions
|
|
7
6
|
import std/Str
|
|
8
7
|
import std/Int
|
|
@@ -93,16 +92,15 @@ fun stepIndex(s: Step) -> Int =
|
|
|
93
92
|
ReadMin => 1
|
|
94
93
|
ReadMax => 2
|
|
95
94
|
|
|
96
|
-
#
|
|
95
|
+
# One call, different code per variant: a method over the variants.
|
|
97
|
-
|
|
96
|
+
enum Book =
|
|
98
|
-
|
|
97
|
+
| Novel(title: Str, pages: Int)
|
|
99
|
-
|
|
98
|
+
| Atlas(title: Str, regions: Int)
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
fun describe(self) -> Str =
|
|
101
|
+
match self
|
|
102
|
-
|
|
102
|
+
Novel(title, _) => "novel {title}"
|
|
103
|
-
|
|
104
|
-
struct Atlas(Book(title), regions: Int) =
|
|
105
|
-
|
|
103
|
+
Atlas(title, _) => "atlas {title}"
|
|
106
104
|
|
|
107
105
|
fun describeAuto(b: Book) -> Str =
|
|
108
106
|
b.describe()
|
|
@@ -111,7 +109,6 @@ fun describeMatch(b: Book) -> Str =
|
|
|
111
109
|
match b
|
|
112
110
|
Novel(title, _) => "novel {title}"
|
|
113
111
|
Atlas(title, _) => "atlas {title}"
|
|
114
|
-
_ => "book"
|
|
115
112
|
|
|
116
113
|
enum Jb = BNull | BBool(v: Bool) | BFloat(v: Float) | BStr(v: Str) | BList(v: Slice[Jb])
|
|
117
114
|
|
|
@@ -210,7 +207,7 @@ test "enum defines its own method over its variants"
|
|
|
210
207
|
b: Shape := Rectangle(w: 1.0, h: 2.0)
|
|
211
208
|
assert b.area() == 2.0
|
|
212
209
|
|
|
213
|
-
test "Book both ways:
|
|
210
|
+
test "Book both ways: method and match agree"
|
|
214
211
|
books: Slice[Book] := Slice[Book]()
|
|
215
212
|
books.push(Novel(title: "Dune", pages: 600))
|
|
216
213
|
books.push(Atlas(title: "World", regions: 7))
|
tests/fbip/aliasing.plum
CHANGED
|
@@ -39,20 +39,12 @@ struct Zero(label: Str, n: Int) =
|
|
|
39
39
|
Origin = Zero(label: "z", n: 0)
|
|
40
40
|
fun inc(self) -> Zero = Zero(..self, n: self.n + 1)
|
|
41
41
|
|
|
42
|
-
# ---- a
|
|
42
|
+
# ---- a method that stores self, then updates a copy ----
|
|
43
43
|
|
|
44
|
-
struct Base(label: Str, n: Int) =
|
|
45
|
-
fun step(self) -> Base = Base(..self, n: self.n + 1)
|
|
46
|
-
|
|
47
|
-
struct
|
|
44
|
+
struct Stored(label: Str, n: Int, log: Slice[Stored]) =
|
|
48
|
-
|
|
45
|
+
fun step(self) -> Stored =
|
|
49
46
|
self.log.push(self)
|
|
50
|
-
|
|
47
|
+
Stored(..self, n: self.n + 1)
|
|
51
|
-
|
|
52
|
-
struct Wide(label: Str, n: Int, extra: Int) =
|
|
53
|
-
fun step(self) -> Wide = Wide(..self, n: self.n + 1)
|
|
54
|
-
|
|
55
|
-
struct Narrow(Wide(label, n, extra), flag: Int)
|
|
56
48
|
|
|
57
49
|
# ---- helpers ----
|
|
58
50
|
|
|
@@ -244,21 +236,13 @@ test "a shared value passed through a method then handed over in a loop"
|
|
|
244
236
|
assert a.n == 0
|
|
245
237
|
assert v.n == 3
|
|
246
238
|
|
|
247
|
-
test "
|
|
239
|
+
test "a method that stores self then spreads it"
|
|
248
|
-
s :=
|
|
240
|
+
s := Stored(label: "s", n: 0, log: Slice[Stored]())
|
|
249
|
-
b: Base := s
|
|
250
|
-
c :=
|
|
241
|
+
c := s.step()
|
|
251
242
|
assert s.log.at(0).n == 0
|
|
243
|
+
assert s.n == 0
|
|
252
244
|
assert c.n == 1
|
|
253
245
|
|
|
254
|
-
test "a base spread of a subtype stays a copy of the base"
|
|
255
|
-
w := Narrow(label: "n", n: 0, extra: 5, flag: 1)
|
|
256
|
-
x: Wide := w
|
|
257
|
-
y := x.step()
|
|
258
|
-
assert w.n == 0
|
|
259
|
-
assert y.n == 1
|
|
260
|
-
assert y.extra == 5
|
|
261
|
-
|
|
262
246
|
test "an update in only one branch with the old value read after"
|
|
263
247
|
a := Cell(label: "a", n: 0)
|
|
264
248
|
assert branchy(a, true) == 1
|
tests/wasm_codegen/e2e_records/base_through_embedded_struct.plum
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Actor(x: Int) =
|
|
7
|
-
fun go(self) -> Int = 1
|
|
8
|
-
|
|
9
|
-
struct Mid(Actor(x), m: Int) =
|
|
10
|
-
override fun go(self) -> Int = 2 + self.Actor.go()
|
|
11
|
-
|
|
12
|
-
struct Top(Mid(x, m), t: Int)
|
|
13
|
-
|
|
14
|
-
fun run(a: Actor) -> Int = a.go()
|
|
15
|
-
|
|
16
|
-
fun runMain() -> Int =
|
|
17
|
-
t := Top(x: 1, m: 2, t: 3)
|
|
18
|
-
run(t) * 10 + t.x
|
|
19
|
-
|
|
20
|
-
# moved from e2e_records::record_embed::base_through_embedded_struct
|
|
21
|
-
test "base through embedded struct"
|
|
22
|
-
assert runMain() == 31
|
tests/wasm_codegen/e2e_records/embedded_record_standalone_and_unselected_set_by_init.plum
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Stage(camScale: Int, tag: Int) =
|
|
7
|
-
fun init(self) =
|
|
8
|
-
self.camX: Int = 6
|
|
9
|
-
|
|
10
|
-
struct World(Stage(tag), score: Int) =
|
|
11
|
-
fun init(self) =
|
|
12
|
-
self.camScale = 5
|
|
13
|
-
|
|
14
|
-
fun runMain() -> Int =
|
|
15
|
-
s := Stage(camScale: 2, tag: 1)
|
|
16
|
-
w := World(tag: 3, score: 1)
|
|
17
|
-
s.camScale * 100 + w.camScale * 10 + w.camX + w.tag + s.camX
|
|
18
|
-
|
|
19
|
-
# moved from e2e_records::record_embed::embedded_record_standalone_and_unselected_set_by_init
|
|
20
|
-
test "embedded record standalone and unselected set by init"
|
|
21
|
-
assert runMain() == 265
|
tests/wasm_codegen/e2e_records/field_less_struct_embedding_field_less_base_dispatches_in_a_slice.plum
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Actor() =
|
|
7
|
-
fun act(self, n: Int) -> Int = n
|
|
8
|
-
fun draw(self) -> Int = 1
|
|
9
|
-
|
|
10
|
-
struct Panel(Actor) =
|
|
11
|
-
override fun draw(self) -> Int = 10
|
|
12
|
-
|
|
13
|
-
struct Ship(Actor, speed: Int) =
|
|
14
|
-
override fun act(self, n: Int) -> Int = n + self.speed
|
|
15
|
-
override fun draw(self) -> Int = 100
|
|
16
|
-
|
|
17
|
-
fun runMain() -> Int =
|
|
18
|
-
xs: Slice[Actor] := Slice[Actor]()
|
|
19
|
-
xs.push(Panel())
|
|
20
|
-
xs.push(Ship(speed: 5))
|
|
21
|
-
total := 0
|
|
22
|
-
for i := range 2
|
|
23
|
-
total = total + xs.at(i).draw() + xs.at(i).act(1000)
|
|
24
|
-
total
|
|
25
|
-
|
|
26
|
-
# moved from e2e_records::empty_record_embed::field_less_struct_embedding_field_less_base_dispatches_in_a_slice
|
|
27
|
-
# Panel: draw 10 + act 1000; Ship: draw 100 + act 1005.
|
|
28
|
-
test "field less struct embedding field less base dispatches in a slice"
|
|
29
|
-
assert runMain() == 2115
|
tests/wasm_codegen/e2e_records/fresh_list_per_construction.plum
CHANGED
|
@@ -10,13 +10,9 @@ struct Actor(px: Int, py: Int) =
|
|
|
10
10
|
self.kids: Slice[Int] = Slice[Int]()
|
|
11
11
|
fun area(self) -> Int = self.width * self.height
|
|
12
12
|
|
|
13
|
-
struct Ship(Actor(px, py)) =
|
|
14
|
-
fun init(self) =
|
|
15
|
-
self.vx = 0
|
|
16
|
-
|
|
17
13
|
fun runMain() -> Int =
|
|
18
|
-
a :=
|
|
14
|
+
a := Actor(px: 0, py: 0)
|
|
19
|
-
b :=
|
|
15
|
+
b := Actor(px: 0, py: 0)
|
|
20
16
|
a.kids.push(1)
|
|
21
17
|
a.kids.push(2)
|
|
22
18
|
b.kids.push(9)
|
tests/wasm_codegen/e2e_records/{inherited_fields_methods_and_init_order.plum → init_fields_and_order.plum}
RENAMED
|
@@ -3,24 +3,18 @@ import std/Int
|
|
|
3
3
|
import std/Float
|
|
4
4
|
import std/Slice
|
|
5
5
|
|
|
6
|
-
struct Stage(camScale: Int) =
|
|
6
|
+
struct Stage(camScale: Int, score: Int) =
|
|
7
7
|
fun init(self) =
|
|
8
8
|
self.root: Int = 10
|
|
9
9
|
self.camX: Int = self.camScale * 2
|
|
10
|
+
self.best: Int = self.root + self.camX + 1
|
|
10
11
|
|
|
11
12
|
fun act(self, dt: Int) -> Int = self.root + dt
|
|
12
13
|
|
|
13
|
-
fun draw(self) -> Int = 100
|
|
14
|
-
|
|
15
|
-
struct World(Stage(camScale), score: Int) =
|
|
16
|
-
fun init(self) =
|
|
17
|
-
self.best: Int = self.root + self.camX + 1
|
|
18
|
-
|
|
19
14
|
fun runMain() -> Int =
|
|
20
|
-
w :=
|
|
15
|
+
w := Stage(camScale: 3, score: 4)
|
|
21
16
|
w.camScale + w.root * 10 + w.camX * 100 + w.score * 1000 + w.best * 10000 + w.act(5) * 100000
|
|
22
17
|
|
|
23
|
-
# moved from e2e_records::record_embed::inherited_fields_methods_and_init_order
|
|
24
18
|
# camScale 3, root 10, camX 6, score 4, best 17, act(5) = 15
|
|
25
|
-
test "
|
|
19
|
+
test "init fields and init order"
|
|
26
20
|
assert runMain() == 1674703
|
tests/wasm_codegen/e2e_records/override_calls_original.plum
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Stage(camScale: Int) =
|
|
7
|
-
fun init(self) =
|
|
8
|
-
self.root: Int = 10
|
|
9
|
-
self.camX: Int = self.camScale * 2
|
|
10
|
-
|
|
11
|
-
fun act(self, dt: Int) -> Int = self.root + dt
|
|
12
|
-
|
|
13
|
-
fun draw(self) -> Int = 100
|
|
14
|
-
|
|
15
|
-
struct World(Stage(camScale), score: Int) =
|
|
16
|
-
override fun draw(self) -> Int = self.Stage.draw() + self.score
|
|
17
|
-
|
|
18
|
-
fun runMain() -> Int =
|
|
19
|
-
w := World(camScale: 1, score: 7)
|
|
20
|
-
w.draw() * 10 + w.act(1)
|
|
21
|
-
|
|
22
|
-
# moved from e2e_records::record_embed::override_calls_original
|
|
23
|
-
test "override calls original"
|
|
24
|
-
assert runMain() == 1081
|
tests/wasm_codegen/e2e_records/ship_example_values.plum
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Actor(px: Int, py: Int) =
|
|
7
|
-
fun init(self) =
|
|
8
|
-
self.width: Int = 10
|
|
9
|
-
self.height: Int = self.width * 2
|
|
10
|
-
self.kids: Slice[Int] = Slice[Int]()
|
|
11
|
-
fun area(self) -> Int = self.width * self.height
|
|
12
|
-
|
|
13
|
-
struct Ship(Actor(px, py), tex: Int) =
|
|
14
|
-
fun init(self) =
|
|
15
|
-
self.height = self.width + 1
|
|
16
|
-
self.vx = 7
|
|
17
|
-
override fun area(self) -> Int = self.width * self.height + self.vx
|
|
18
|
-
|
|
19
|
-
fun runMain() -> Int =
|
|
20
|
-
s := Ship(px: 1, py: 2, tex: 3)
|
|
21
|
-
s.px + s.py * 10 + s.tex * 100 + s.area() * 1000
|
|
22
|
-
|
|
23
|
-
# moved from e2e_records::record_init::ship_example_values
|
|
24
|
-
# px 1 + py 20 + tex 300 + (10*11+7)*1000
|
|
25
|
-
test "ship example values"
|
|
26
|
-
assert runMain() == 117321
|
tests/wasm_codegen/e2e_records/through_a_base_value.plum
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import std/Str
|
|
2
|
-
import std/Int
|
|
3
|
-
import std/Float
|
|
4
|
-
import std/Slice
|
|
5
|
-
|
|
6
|
-
struct Actor(px: Int, width: Int) =
|
|
7
|
-
fun init(self) =
|
|
8
|
-
self.width = 10
|
|
9
|
-
fun area(self) -> Int = self.width * 2
|
|
10
|
-
|
|
11
|
-
struct Ship(Actor(px), tex: Int) =
|
|
12
|
-
override fun area(self) -> Int = self.width * 20
|
|
13
|
-
|
|
14
|
-
struct Rock(Actor(px))
|
|
15
|
-
|
|
16
|
-
fun total(a: Actor) -> Int = a.area() + a.px
|
|
17
|
-
|
|
18
|
-
fun runMain() -> Int =
|
|
19
|
-
total(Ship(px: 1, tex: 0)) * 1000 + total(Rock(px: 2))
|
|
20
|
-
|
|
21
|
-
# moved from e2e_records::record_init::through_a_base_value
|
|
22
|
-
# Ship: 10*20 + 1 = 201 ; Rock: 20 + 2 = 22
|
|
23
|
-
test "through a base value"
|
|
24
|
-
assert runMain() == 201022
|