plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
ed7f16c
— Peter John
2026-11-02T19:46:13+05:30
T5: field access through a trait
- plum-checker/src/unions.rs +76 -3
- plum-checker/tests/trait_fields_tests.rs +223 -0
- plum-core/src/union_lower.rs +52 -0
- plum-core/src/unions.rs +25 -4
plum-checker/src/unions.rs
CHANGED
|
@@ -554,9 +554,38 @@ pub fn restorePlan(p: &plan::UnionPlan) {
|
|
|
554
554
|
for (a, vs) in &p.dispatches {
|
|
555
555
|
plan::recordDispatch(*a, vs.clone());
|
|
556
556
|
}
|
|
557
|
+
for (a, vs) in &p.fieldReads {
|
|
558
|
+
plan::recordFieldRead(*a, vs.clone());
|
|
559
|
+
}
|
|
560
|
+
for (a, vs) in &p.fieldWrites {
|
|
561
|
+
plan::recordFieldWrite(*a, vs.clone());
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/// Names of the fields every member of the union has an accessor for.
|
|
566
|
+
fn commonFields(ms: &[(String, PlumType)], ctx: &CheckCtx) -> Option<Vec<String>> {
|
|
567
|
+
let mut common: Option<Vec<String>> = None;
|
|
568
|
+
for (_, mt) in ms {
|
|
569
|
+
let recv = crate::methodReceiverName(mt)?;
|
|
570
|
+
let mut mine: Vec<String> = ctx
|
|
571
|
+
.methods
|
|
572
|
+
.keys()
|
|
573
|
+
.filter(|(r, n)| *r == recv && n.starts_with("__get_"))
|
|
574
|
+
.map(|(_, n)| n["__get_".len()..].to_string())
|
|
575
|
+
.collect();
|
|
576
|
+
mine.sort();
|
|
577
|
+
common = Some(match common {
|
|
578
|
+
None => mine,
|
|
579
|
+
Some(c) => c.into_iter().filter(|f| mine.contains(f)).collect(),
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
common.filter(|c| !c.is_empty())
|
|
557
583
|
}
|
|
558
584
|
|
|
559
|
-
/// Field access through a trait value
|
|
585
|
+
/// Field access through a trait value: the field's type, with the read (or
|
|
586
|
+
/// write, when `write`) recorded for `union_lower` to turn into an accessor
|
|
587
|
+
/// dispatch. `None`: `obj` is not a trait. `exprAddr` is the address of the
|
|
588
|
+
/// `Attribute` expression for a read, of the assign statement for a write.
|
|
560
589
|
pub fn traitField(
|
|
561
590
|
obj: &PlumType,
|
|
562
591
|
field: &str,
|
|
@@ -564,6 +593,50 @@ pub fn traitField(
|
|
|
564
593
|
write: bool,
|
|
565
594
|
ctx: &CheckCtx,
|
|
566
595
|
) -> Option<Result<PlumType, String>> {
|
|
596
|
+
let (en, args) = enumOf(obj)?;
|
|
597
|
+
let ms = members(en, args, ctx);
|
|
598
|
+
if ms.is_empty() {
|
|
599
|
+
return None;
|
|
600
|
+
}
|
|
601
|
+
let fields = commonFields(&ms, ctx)?;
|
|
602
|
+
if !fields.iter().any(|f| f == field) {
|
|
603
|
+
return Some(Err(format!("trait {en} has no field {field}")));
|
|
604
|
+
}
|
|
605
|
+
let acc = plum_core::traits::getter(field);
|
|
606
|
+
let ty = match memberSig(&ms[0].1, &acc, ctx) {
|
|
607
|
+
Some(PlumType::TFun(_, ret)) => *ret,
|
|
608
|
+
_ => return Some(Err(format!("trait {en} has no field {field}"))),
|
|
609
|
+
};
|
|
610
|
+
let variants: Vec<String> = ms.into_iter().map(|(v, _)| v).collect();
|
|
611
|
+
if write {
|
|
612
|
+
plan::recordFieldWrite(exprAddr, variants);
|
|
613
|
+
} else {
|
|
567
|
-
|
|
614
|
+
plan::recordFieldRead(exprAddr, variants);
|
|
615
|
+
}
|
|
616
|
+
Some(Ok(ty))
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/// For a field-assignment target `object.x = ..` whose `object` (already
|
|
620
|
+
/// inferred, type `objTy`) is itself a trait field read of a struct type:
|
|
621
|
+
/// the error to report, since writing into a struct copy would be lost.
|
|
622
|
+
pub fn nestedTraitWrite(
|
|
623
|
+
object: &ast::Expr,
|
|
624
|
+
objTy: &PlumType,
|
|
625
|
+
ctx: &CheckCtx,
|
|
626
|
+
) -> Option<String> {
|
|
627
|
+
if !plan::hasFieldRead(object as *const ast::Expr as usize) {
|
|
568
|
-
|
|
628
|
+
return None;
|
|
629
|
+
}
|
|
630
|
+
let PlumType::TNamed(n) = objTy else { return None };
|
|
631
|
+
if !ctx.enum_variants.get(n).is_some_and(|i| i.is_value) {
|
|
632
|
+
return None;
|
|
633
|
+
}
|
|
634
|
+
let ast::Expr::Attribute(at) = object else { return None };
|
|
635
|
+
let ast::AttrKind::Field(f) = &at.attr else { return None };
|
|
636
|
+
let base = match &at.object {
|
|
637
|
+
ast::Expr::Var(v) => v.clone(),
|
|
638
|
+
ast::Expr::Self_ => "self".to_string(),
|
|
639
|
+
_ => "e".to_string(),
|
|
640
|
+
};
|
|
641
|
+
Some(format!("assign the whole field: {base}.{f} = ..."))
|
|
569
642
|
}
|
plum-checker/tests/trait_fields_tests.rs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#![allow(non_snake_case)]
|
|
2
|
+
//! Trait field reads/writes: the checker's `traitField` plus `union_lower`'s
|
|
3
|
+
//! rewrite of the recorded reads and writes into `__get_f` / `__set_f` dispatch.
|
|
4
|
+
use plum_checker::types::PlumType;
|
|
5
|
+
use plum_checker::unions::{nestedTraitWrite, traitField};
|
|
6
|
+
use plum_checker::{
|
|
7
|
+
buildGlobalTables, buildMinRequiredArgs, buildPrimitiveGenerics, buildStaticMethods,
|
|
8
|
+
checkSource, CheckCtx,
|
|
9
|
+
};
|
|
10
|
+
use plum_core::ast::*;
|
|
11
|
+
use plum_core::traits::lowerTraits;
|
|
12
|
+
use plum_core::union_lower::applyPlan;
|
|
13
|
+
use plum_core::unions::{beginPlan, resolveMembers, takePlan};
|
|
14
|
+
use plum_core::AstParser;
|
|
15
|
+
|
|
16
|
+
const DEFS: &str = "\
|
|
17
|
+
struct Vec2(x: Float, y: Float)
|
|
18
|
+
trait Ent(pos: Vec2, z: Float) =
|
|
19
|
+
fun draw(self) -> Float
|
|
20
|
+
|
|
21
|
+
record Hut(Ent, k: Float) =
|
|
22
|
+
fun draw(self) -> Float = self.z
|
|
23
|
+
|
|
24
|
+
record Orc(Ent) =
|
|
25
|
+
fun draw(self) -> Float = self.z + 1.0
|
|
26
|
+
|
|
27
|
+
record Circle(r: Float)
|
|
28
|
+
record Square(s: Float)
|
|
29
|
+
enum Shape = Circle | Square
|
|
30
|
+
";
|
|
31
|
+
|
|
32
|
+
fn lowered(body: &str) -> Source {
|
|
33
|
+
let src = format!("{DEFS}\n{body}\n");
|
|
34
|
+
let mut parser = tree_sitter::Parser::new();
|
|
35
|
+
parser
|
|
36
|
+
.set_language(&tree_sitter_plum::LANGUAGE.into())
|
|
37
|
+
.unwrap();
|
|
38
|
+
let tree = parser.parse(&src, None).unwrap();
|
|
39
|
+
assert!(!tree.root_node().has_error(), "{}", tree.root_node().to_sexp());
|
|
40
|
+
let mut s = AstParser::new(&src).parseSourceRaw(tree.root_node());
|
|
41
|
+
assert_eq!(lowerTraits(&mut s), Vec::<String>::new());
|
|
42
|
+
let errs = resolveMembers(&mut s);
|
|
43
|
+
assert!(errs.is_empty(), "{errs:?}");
|
|
44
|
+
s
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn withCtx<R>(s: &Source, f: impl FnOnce(&CheckCtx) -> R) -> R {
|
|
48
|
+
let (_, methods, enum_variants, enum_params) = buildGlobalTables(s);
|
|
49
|
+
let min_required = buildMinRequiredArgs(s);
|
|
50
|
+
let primitive_generics = buildPrimitiveGenerics(s);
|
|
51
|
+
let statics = buildStaticMethods(s);
|
|
52
|
+
let ctx = CheckCtx {
|
|
53
|
+
methods: &methods,
|
|
54
|
+
enum_variants: &enum_variants,
|
|
55
|
+
enum_params: &enum_params,
|
|
56
|
+
min_required: &min_required,
|
|
57
|
+
primitive_generics: &primitive_generics,
|
|
58
|
+
statics: Some(&statics),
|
|
59
|
+
};
|
|
60
|
+
f(&ctx)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn named(n: &str) -> PlumType {
|
|
64
|
+
PlumType::TNamed(n.to_string())
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn fnBody<'a>(s: &'a Source, name: &str) -> &'a FnBody {
|
|
68
|
+
s.items
|
|
69
|
+
.iter()
|
|
70
|
+
.find_map(|i| match i {
|
|
71
|
+
Item::Fn(f) if f.name == name && f.type_param.is_none() => Some(&f.body),
|
|
72
|
+
_ => None,
|
|
73
|
+
})
|
|
74
|
+
.unwrap()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn firstStmt<'a>(s: &'a Source, name: &str) -> &'a Stmt {
|
|
78
|
+
let FnBody::Block(b) = fnBody(s, name) else { panic!("block") };
|
|
79
|
+
&b.stmts[0]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn hasMethod(s: &Source, recv: &str, name: &str) -> bool {
|
|
83
|
+
s.items.iter().any(
|
|
84
|
+
|i| matches!(i, Item::Fn(f) if f.type_param.as_deref() == Some(recv) && f.name == name),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn read_type_and_plan() {
|
|
90
|
+
let s = lowered("");
|
|
91
|
+
withCtx(&s, |ctx| {
|
|
92
|
+
beginPlan();
|
|
93
|
+
let r = traitField(&named("Ent"), "pos", 7, false, ctx);
|
|
94
|
+
assert_eq!(r, Some(Ok(named("Vec2"))));
|
|
95
|
+
let p = takePlan();
|
|
96
|
+
assert_eq!(p.fieldReads[&7], vec!["Ent__Hut", "Ent__Orc"]);
|
|
97
|
+
assert!(p.fieldWrites.is_empty());
|
|
98
|
+
beginPlan();
|
|
99
|
+
assert_eq!(traitField(&named("Ent"), "z", 9, true, ctx), Some(Ok(PlumType::TFloat)));
|
|
100
|
+
let p = takePlan();
|
|
101
|
+
assert_eq!(p.fieldWrites[&9], vec!["Ent__Hut", "Ent__Orc"]);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn unknown_field_and_non_trait() {
|
|
107
|
+
let s = lowered("");
|
|
108
|
+
withCtx(&s, |ctx| {
|
|
109
|
+
beginPlan();
|
|
110
|
+
assert_eq!(
|
|
111
|
+
traitField(&named("Ent"), "nope", 1, false, ctx),
|
|
112
|
+
Some(Err("trait Ent has no field nope".to_string()))
|
|
113
|
+
);
|
|
114
|
+
assert_eq!(traitField(&named("Shape"), "r", 1, false, ctx), None);
|
|
115
|
+
assert_eq!(traitField(&named("Hut"), "z", 1, false, ctx), None);
|
|
116
|
+
assert_eq!(traitField(&PlumType::TInt, "z", 1, false, ctx), None);
|
|
117
|
+
assert!(takePlan().fieldReads.is_empty());
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
fn nested_write_rejected() {
|
|
123
|
+
let s = lowered("fun f(e: Ent) =\n e.pos.x = 1.0\n");
|
|
124
|
+
// The target is Field(Attribute(e, pos), x).
|
|
125
|
+
let Stmt::Assign(a) = firstStmt(&s, "f") else { panic!() };
|
|
126
|
+
let AssignTarget::Field(obj, _) = &a.targets[0] else { panic!() };
|
|
127
|
+
withCtx(&s, |ctx| {
|
|
128
|
+
beginPlan();
|
|
129
|
+
let objAddr = &**obj as *const Expr as usize;
|
|
130
|
+
assert_eq!(nestedTraitWrite(obj, &named("Vec2"), ctx), None); // nothing read yet
|
|
131
|
+
traitField(&named("Ent"), "pos", objAddr, false, ctx).unwrap().unwrap();
|
|
132
|
+
assert_eq!(
|
|
133
|
+
nestedTraitWrite(obj, &named("Vec2"), ctx),
|
|
134
|
+
Some("assign the whole field: e.pos = ...".to_string())
|
|
135
|
+
);
|
|
136
|
+
// A non-struct field type (a record) is shared, so writing into it is fine.
|
|
137
|
+
assert_eq!(nestedTraitWrite(obj, &named("Hut"), ctx), None);
|
|
138
|
+
takePlan();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// `get` / `set` / `bump` lowered by hand-recorded plans (the checker call sites
|
|
143
|
+
/// live in lib.rs); the result still type-checks.
|
|
144
|
+
#[test]
|
|
145
|
+
fn lowering_rewrites_reads_and_writes() {
|
|
146
|
+
let mut s = lowered(
|
|
147
|
+
"fun get(e: Ent) -> Float =\n e.z\n\nfun set(e: Ent) =\n e.z = 5.0\n\nfun bump(e: Ent) =\n e.z = e.z + 1.0\n",
|
|
148
|
+
);
|
|
149
|
+
withCtx(&s, |ctx| {
|
|
150
|
+
beginPlan();
|
|
151
|
+
let getAddr = match fnBody(&s, "get") {
|
|
152
|
+
FnBody::Expr(e) => e as *const Expr as usize,
|
|
153
|
+
FnBody::Block(b) => match &b.stmts[0] {
|
|
154
|
+
Stmt::Return(Some(e)) | Stmt::Expr(e, ..) => e as *const Expr as usize,
|
|
155
|
+
_ => panic!(),
|
|
156
|
+
},
|
|
157
|
+
_ => panic!(),
|
|
158
|
+
};
|
|
159
|
+
traitField(&named("Ent"), "z", getAddr, false, ctx).unwrap().unwrap();
|
|
160
|
+
let setAddr = firstStmt(&s, "set") as *const Stmt as usize;
|
|
161
|
+
traitField(&named("Ent"), "z", setAddr, true, ctx).unwrap().unwrap();
|
|
162
|
+
// bump: read inside the value, write on the statement.
|
|
163
|
+
let bumpStmt = firstStmt(&s, "bump");
|
|
164
|
+
let Stmt::Assign(a) = bumpStmt else { panic!() };
|
|
165
|
+
let Expr::Binary(bin) = &a.values[0] else { panic!() };
|
|
166
|
+
traitField(&named("Ent"), "z", &bin.left as *const Expr as usize, false, ctx)
|
|
167
|
+
.unwrap()
|
|
168
|
+
.unwrap();
|
|
169
|
+
traitField(&named("Ent"), "z", bumpStmt as *const Stmt as usize, true, ctx)
|
|
170
|
+
.unwrap()
|
|
171
|
+
.unwrap();
|
|
172
|
+
});
|
|
173
|
+
let plan = takePlan();
|
|
174
|
+
applyPlan(&mut s, &plan);
|
|
175
|
+
// Accessor dispatch exists on the trait union.
|
|
176
|
+
assert!(hasMethod(&s, "Ent", "__get_z"));
|
|
177
|
+
assert!(hasMethod(&s, "Ent", "__set_z"));
|
|
178
|
+
// `set` is now a call `e.__set_z(5.0)`.
|
|
179
|
+
let Stmt::Expr(Expr::Attribute(at), ..) = firstStmt(&s, "set") else {
|
|
180
|
+
panic!("{:?}", firstStmt(&s, "set"))
|
|
181
|
+
};
|
|
182
|
+
let AttrKind::Method(c) = &at.attr else { panic!() };
|
|
183
|
+
assert_eq!(c.name, "__set_z");
|
|
184
|
+
// `bump` is `e.__set_z(e.__get_z() + 1.0)`: get then set.
|
|
185
|
+
let Stmt::Expr(Expr::Attribute(at), ..) = firstStmt(&s, "bump") else { panic!() };
|
|
186
|
+
let AttrKind::Method(c) = &at.attr else { panic!() };
|
|
187
|
+
assert_eq!(c.name, "__set_z");
|
|
188
|
+
let Arg::Positional(Expr::Binary(b)) = &c.args[0] else { panic!() };
|
|
189
|
+
let Expr::Attribute(g) = &b.left else { panic!() };
|
|
190
|
+
assert!(matches!(&g.attr, AttrKind::Method(m) if m.name == "__get_z"));
|
|
191
|
+
// The rewritten program is well typed.
|
|
192
|
+
checkSource(&s).unwrap_or_else(|e| panic!("{:?}", e.iter().map(|x| &x.message).collect::<Vec<_>>()));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn plain_union_unaffected() {
|
|
197
|
+
let mut s = lowered("fun f(x: Shape) -> Shape =\n x\n");
|
|
198
|
+
let before = s.clone();
|
|
199
|
+
applyPlan(&mut s, &takePlan());
|
|
200
|
+
assert_eq!(s, before);
|
|
201
|
+
withCtx(&s, |ctx| {
|
|
202
|
+
beginPlan();
|
|
203
|
+
assert_eq!(traitField(&named("Shape"), "r", 3, false, ctx), None);
|
|
204
|
+
assert!(takePlan().isEmpty());
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// Through the checker proper (needs the `traitField` call sites in lib.rs and
|
|
209
|
+
/// `UnionPlan::isEmpty` in `checkUnions`).
|
|
210
|
+
#[test]
|
|
211
|
+
#[ignore = "needs T4 call sites"]
|
|
212
|
+
fn typed_source_lowers_field_access() {
|
|
213
|
+
let s = lowered(
|
|
214
|
+
"fun bump(e: Ent) -> Float =\n e.z = e.z + 1.0\n e.z\n\nfun nest(e: Ent) =\n e.pos.x = 1.0\n",
|
|
215
|
+
);
|
|
216
|
+
let errs = plum_checker::typedSource(&s).err().expect("nested write must be rejected");
|
|
217
|
+
assert!(errs.iter().any(|e| e.message.contains("assign the whole field: e.pos = ...")), "{:?}", errs.iter().map(|e| &e.message).collect::<Vec<_>>());
|
|
218
|
+
let mut ok = s.clone();
|
|
219
|
+
ok.items.retain(|i| !matches!(i, Item::Fn(f) if f.name == "nest"));
|
|
220
|
+
let info = plum_checker::typedSource(&ok).unwrap_or_else(|e| panic!("{:?}", e.iter().map(|x| &x.message).collect::<Vec<_>>()));
|
|
221
|
+
let low = info.lowered.expect("lowered copy");
|
|
222
|
+
assert!(hasMethod(&low, "Ent", "__get_z") && hasMethod(&low, "Ent", "__set_z"));
|
|
223
|
+
}
|
plum-core/src/union_lower.rs
CHANGED
|
@@ -64,6 +64,7 @@ impl<'a> Lowerer<'a> {
|
|
|
64
64
|
|
|
65
65
|
fn stmt(&mut self, s: &mut Stmt) {
|
|
66
66
|
let mark = self.pending.len();
|
|
67
|
+
let stmtAddr = addr(s);
|
|
67
68
|
match s {
|
|
68
69
|
Stmt::Assign(a) => {
|
|
69
70
|
for t in &mut a.targets {
|
|
@@ -110,9 +111,46 @@ impl<'a> Lowerer<'a> {
|
|
|
110
111
|
Stmt::Return(Some(e)) | Stmt::Crash(e) | Stmt::Expr(e, ..) => self.expr(e),
|
|
111
112
|
Stmt::Return(None) | Stmt::Break | Stmt::Continue | Stmt::Todo => {}
|
|
112
113
|
}
|
|
114
|
+
self.lowerFieldWrite(s, stmtAddr);
|
|
113
115
|
self.pending.truncate(mark);
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
/// `e.f = v` on a trait value becomes the dispatch call `e.__set_f(v)`.
|
|
119
|
+
fn lowerFieldWrite(&mut self, s: &mut Stmt, stmtAddr: usize) {
|
|
120
|
+
let Stmt::Assign(a) = s else { return };
|
|
121
|
+
let inner = addr(a);
|
|
122
|
+
let Some(variants) = self
|
|
123
|
+
.plan
|
|
124
|
+
.fieldWrites
|
|
125
|
+
.get(&stmtAddr)
|
|
126
|
+
.or_else(|| self.plan.fieldWrites.get(&inner))
|
|
127
|
+
.cloned()
|
|
128
|
+
else {
|
|
129
|
+
return;
|
|
130
|
+
};
|
|
131
|
+
if a.targets.len() != 1 || a.values.len() != 1 || a.declare {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let AssignTarget::Field(_, f) = &a.targets[0] else { return };
|
|
135
|
+
let method = crate::traits::setter(f);
|
|
136
|
+
let AssignTarget::Field(obj, _) = a.targets.remove(0) else { return };
|
|
137
|
+
let value = a.values.remove(0);
|
|
138
|
+
self.needs.push((variants, method.clone()));
|
|
139
|
+
*s = Stmt::Expr(
|
|
140
|
+
Expr::Attribute(Box::new(AttributeExpr {
|
|
141
|
+
object: *obj,
|
|
142
|
+
attr: AttrKind::Method(FnCall {
|
|
143
|
+
name: method,
|
|
144
|
+
args: vec![Arg::Positional(value)],
|
|
145
|
+
line: 0,
|
|
146
|
+
column: 0,
|
|
147
|
+
}),
|
|
148
|
+
})),
|
|
149
|
+
0,
|
|
150
|
+
0,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
116
154
|
fn matchStmt(&mut self, m: &mut Match) {
|
|
117
155
|
for s in &mut m.subjects {
|
|
118
156
|
self.expr(s);
|
|
@@ -268,6 +306,20 @@ impl<'a> Lowerer<'a> {
|
|
|
268
306
|
| Expr::Var(_)
|
|
269
307
|
| Expr::TypeName(_) => {}
|
|
270
308
|
}
|
|
309
|
+
if let Some(variants) = self.plan.fieldReads.get(&a) {
|
|
310
|
+
if let Expr::Attribute(at) = e {
|
|
311
|
+
if let AttrKind::Field(f) = &at.attr {
|
|
312
|
+
let method = crate::traits::getter(f);
|
|
313
|
+
self.needs.push((variants.clone(), method.clone()));
|
|
314
|
+
at.attr = AttrKind::Method(FnCall {
|
|
315
|
+
name: method,
|
|
316
|
+
args: vec![],
|
|
317
|
+
line: 0,
|
|
318
|
+
column: 0,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
271
323
|
if let Some(variants) = self.plan.dispatches.get(&a) {
|
|
272
324
|
let method = match e {
|
|
273
325
|
Expr::Attribute(at) => match &at.attr {
|
plum-core/src/unions.rs
CHANGED
|
@@ -153,6 +153,22 @@ pub struct UnionPlan {
|
|
|
153
153
|
pub narrowings: HashMap<usize, (String, String, String)>,
|
|
154
154
|
/// call address -> hidden variants to dispatch over
|
|
155
155
|
pub dispatches: HashMap<usize, Vec<String>>,
|
|
156
|
+
/// trait field read (`Attribute(e, f)`) address -> hidden variants to dispatch `__get_f` over
|
|
157
|
+
pub fieldReads: HashMap<usize, Vec<String>>,
|
|
158
|
+
/// trait field write (assign statement) address -> hidden variants to dispatch `__set_f` over
|
|
159
|
+
pub fieldWrites: HashMap<usize, Vec<String>>,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
impl UnionPlan {
|
|
163
|
+
/// Nothing was recorded.
|
|
164
|
+
pub fn isEmpty(&self) -> bool {
|
|
165
|
+
self.wraps.is_empty()
|
|
166
|
+
&& self.patterns.is_empty()
|
|
167
|
+
&& self.narrowings.is_empty()
|
|
168
|
+
&& self.dispatches.is_empty()
|
|
169
|
+
&& self.fieldReads.is_empty()
|
|
170
|
+
&& self.fieldWrites.is_empty()
|
|
171
|
+
}
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
thread_local! {
|
|
@@ -197,11 +213,16 @@ pub fn takePlan() -> UnionPlan {
|
|
|
197
213
|
PLAN.with(|p| std::mem::take(&mut *p.borrow_mut()))
|
|
198
214
|
}
|
|
199
215
|
|
|
200
|
-
/// Records a trait field read at `exprAddr`
|
|
216
|
+
/// Records a trait field read at `exprAddr`.
|
|
201
217
|
pub fn recordFieldRead(exprAddr: usize, variants: Vec<String>) {
|
|
202
|
-
|
|
218
|
+
PLAN.with(|p| p.borrow_mut().fieldReads.insert(exprAddr, variants));
|
|
203
219
|
}
|
|
204
|
-
/// Records a trait field write at `stmtAddr` (
|
|
220
|
+
/// Records a trait field write at `stmtAddr` (the address of the assign statement
|
|
221
|
+
/// or of its `Assign`).
|
|
205
222
|
pub fn recordFieldWrite(stmtAddr: usize, variants: Vec<String>) {
|
|
206
|
-
|
|
223
|
+
PLAN.with(|p| p.borrow_mut().fieldWrites.insert(stmtAddr, variants));
|
|
224
|
+
}
|
|
225
|
+
/// Whether a trait field read is recorded at `exprAddr`.
|
|
226
|
+
pub fn hasFieldRead(exprAddr: usize) -> bool {
|
|
227
|
+
PLAN.with(|p| p.borrow().fieldReads.contains_key(&exprAddr))
|
|
207
228
|
}
|