Skip to content

Commit 5e30629

Browse files
committed
Improve type inference and error reporting
Signed-off-by: Enrico Ghiorzi <[email protected]>
1 parent 38c6758 commit 5e30629

File tree

1 file changed

+117
-68
lines changed

1 file changed

+117
-68
lines changed

scan_scxml/src/builder.rs

Lines changed: 117 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct FsmBuilder {
3636
#[derive(Debug, Clone)]
3737
struct EventBuilder {
3838
// Associates parameter's name with its type's name.
39+
name: String,
3940
params: BTreeMap<String, String>,
4041
senders: HashSet<PgId>,
4142
receivers: HashSet<PgId>,
@@ -172,6 +173,7 @@ impl ModelBuilder {
172173
self.event_indexes.get(id).cloned().unwrap_or_else(|| {
173174
let index = self.events.len();
174175
self.events.push(EventBuilder {
176+
name: id.to_string(),
175177
params: BTreeMap::new(),
176178
index,
177179
senders: HashSet::new(),
@@ -198,7 +200,7 @@ impl ModelBuilder {
198200
fn prebuild_processes(&mut self, parser: &mut Parser) -> anyhow::Result<()> {
199201
for (id, fsm) in parser.process_list.iter_mut() {
200202
let pg_id = self.fsm_builder(id).pg_id;
201-
self.prebuild_fsms(pg_id, fsm, &parser.interner)
203+
self.prebuild_fsm(pg_id, fsm, &parser.interner)
202204
.with_context(|| {
203205
format!(
204206
"failed pre-processing of fsm {}",
@@ -208,10 +210,18 @@ impl ModelBuilder {
208210
)
209211
})?;
210212
}
213+
for eb in &self.events {
214+
for (param, t) in &eb.params {
215+
// Mark with empty string parameters without known type
216+
if t.is_empty() {
217+
bail!("param {param} of event {} needs type annotation", eb.name);
218+
}
219+
}
220+
}
211221
Ok(())
212222
}
213223

214-
fn prebuild_fsms(
224+
fn prebuild_fsm(
215225
&mut self,
216226
pg_id: PgId,
217227
fmt: &mut Scxml,
@@ -290,7 +300,9 @@ impl ModelBuilder {
290300
for param in params {
291301
// Update OMG_type value so that it contains its type for sure
292302
let builder = self.events.get_mut(event_index).expect("index must exist");
293-
if let Some(t) = builder.params.get(&param.name) {
303+
if let Some(t) = builder.params.get(&param.name)
304+
&& !t.is_empty()
305+
{
294306
if let Some(omg) = param.omg_type.as_ref() {
295307
if t != omg {
296308
bail!(
@@ -307,6 +319,10 @@ impl ModelBuilder {
307319
let _ = param.omg_type.insert(t.clone());
308320
let builder = self.events.get_mut(event_index).expect("index must exist");
309321
builder.params.insert(param.name.clone(), t);
322+
} else {
323+
// Mark with empty string parameters without known type
324+
let builder = self.events.get_mut(event_index).expect("index must exist");
325+
builder.params.insert(param.name.clone(), String::new());
310326
}
311327
}
312328
Ok(())
@@ -399,7 +415,9 @@ impl ModelBuilder {
399415
))
400416
}
401417
}
402-
boa_ast::expression::operator::binary::BinaryOp::Bitwise(_) => todo!(),
418+
boa_ast::expression::operator::binary::BinaryOp::Bitwise(_) => {
419+
Err(anyhow!("bitwise operations not supported"))
420+
}
403421
boa_ast::expression::operator::binary::BinaryOp::Relational(_)
404422
| boa_ast::expression::operator::binary::BinaryOp::Logical(_) => {
405423
Ok(String::from("bool"))
@@ -409,6 +427,48 @@ impl ModelBuilder {
409427
)),
410428
}
411429
}
430+
boa_ast::Expression::Call(call) => self.infer_type(call.function(), types, interner),
431+
boa_ast::Expression::PropertyAccess(property_access) => match property_access {
432+
boa_ast::expression::access::PropertyAccess::Simple(simple_property_access) => {
433+
if let &boa_ast::Expression::Identifier(ident) = simple_property_access.target()
434+
{
435+
if ident.sym() == interner.get("Math").unwrap() {
436+
match simple_property_access.field() {
437+
boa_ast::expression::access::PropertyAccessField::Const(
438+
identifier,
439+
) => {
440+
if identifier.sym() == interner.get("floor").unwrap() {
441+
Ok(String::from("int32"))
442+
} else if identifier.sym() == interner.get("random").unwrap() {
443+
Ok(String::from("float64"))
444+
} else {
445+
Err(anyhow!(
446+
"unknown expression '{expr:?}', unable to infer type"
447+
))
448+
}
449+
}
450+
boa_ast::expression::access::PropertyAccessField::Expr(
451+
_expression,
452+
) => todo!(),
453+
}
454+
} else {
455+
Err(anyhow!(
456+
"unknown expression '{expr:?}', unable to infer type"
457+
))
458+
}
459+
} else {
460+
Err(anyhow!(
461+
"unknown expression '{expr:?}', unable to infer type"
462+
))
463+
}
464+
}
465+
boa_ast::expression::access::PropertyAccess::Private(_private_property_access) => {
466+
todo!()
467+
}
468+
boa_ast::expression::access::PropertyAccess::Super(_super_property_access) => {
469+
todo!()
470+
}
471+
},
412472
_ => Err(anyhow!(
413473
"unknown expression '{expr:?}', unable to infer type"
414474
)),
@@ -1023,17 +1083,21 @@ impl ModelBuilder {
10231083
// Pass parameters. This could fail due to param content.
10241084
if !send_params.is_empty() {
10251085
// Updates next location.
1026-
next_loc = self.send_params(
1027-
pg_id,
1028-
target_id,
1029-
&send_params,
1030-
event_idx,
1031-
next_loc,
1032-
vars,
1033-
origin,
1034-
params,
1035-
interner,
1036-
)?;
1086+
next_loc = self
1087+
.send_params(
1088+
pg_id,
1089+
target_id,
1090+
&send_params,
1091+
event_idx,
1092+
next_loc,
1093+
vars,
1094+
origin,
1095+
params,
1096+
interner,
1097+
)
1098+
.with_context(|| {
1099+
anyhow!("failed sending params for event {event}")
1100+
})?;
10371101
}
10381102
// Once sending event and args done, get to exit-point
10391103
self.cs
@@ -1183,6 +1247,12 @@ impl ModelBuilder {
11831247
interner: &Interner,
11841248
) -> Result<Location, anyhow::Error> {
11851249
assert!(!params.is_empty());
1250+
// Check that no param is missing
1251+
for p in self.events[event_idx].params.keys() {
1252+
if !params.iter().any(|param| param.name == *p) {
1253+
bail!("missing param {p}");
1254+
}
1255+
}
11861256
let mut exprs = Vec::new();
11871257
let mut scan_types = Vec::new();
11881258
for param in params {
@@ -1284,7 +1354,9 @@ impl ModelBuilder {
12841354
}
12851355
boa_ast::Expression::PropertyAccess(prop_acc) => {
12861356
let expr = &boa_ast::Expression::PropertyAccess(prop_acc.to_owned());
1287-
let ecma_obj = self.expression_prop_access(expr, interner, vars, origin, params)?;
1357+
let ecma_obj = self
1358+
.expression_prop_access(expr, interner, vars, origin, params)
1359+
.context("failed building expression with property access")?;
12881360
// WARN: If the EcmaObj is a primitive SCAN data, we return that.
12891361
// If it is a dictionary of properties, instead, we have no way to represent it properly as a SCAN type.
12901362
match ecma_obj {
@@ -1470,61 +1542,38 @@ impl ModelBuilder {
14701542
boa_ast::Expression::Identifier(ident) => {
14711543
let ident = ident.to_interned_string(interner);
14721544
match ident.as_str() {
1473-
"_event" => Ok(EcmaObj::Properties(HashMap::from_iter(
1474-
[
1475-
(
1476-
String::from("origin"),
1477-
EcmaObj::PrimitiveData(
1478-
Expression::Var(
1479-
origin
1480-
.cloned()
1481-
.ok_or(anyhow!("missing origin of _event"))?,
1482-
Type::Integer,
1483-
),
1484-
String::from("int32"),
1545+
"_event" => Ok(EcmaObj::Properties(HashMap::from_iter([
1546+
(
1547+
String::from("origin"),
1548+
EcmaObj::PrimitiveData(
1549+
Expression::Var(
1550+
origin.cloned().ok_or(anyhow!("missing origin of _event"))?,
1551+
Type::Integer,
14851552
),
1553+
String::from("int32"),
14861554
),
1487-
(
1488-
String::from("data"),
1489-
EcmaObj::Properties(HashMap::from_iter(params.iter().map(
1490-
|(n, (v, t))| {
1491-
(
1492-
n.to_owned(),
1493-
EcmaObj::PrimitiveData(
1494-
Expression::Var(
1495-
v.clone(),
1496-
self.types
1497-
.get(t)
1498-
.map(|(_, t)| t.to_owned())
1499-
.expect("type of data parameter"),
1500-
),
1501-
t.to_owned(),
1555+
),
1556+
(
1557+
String::from("data"),
1558+
EcmaObj::Properties(HashMap::from_iter(params.iter().map(
1559+
|(n, (v, t))| {
1560+
(
1561+
n.to_owned(),
1562+
EcmaObj::PrimitiveData(
1563+
Expression::Var(
1564+
v.clone(),
1565+
self.types
1566+
.get(t)
1567+
.map(|(_, t)| t.to_owned())
1568+
.expect("type of data parameter"),
15021569
),
1503-
)
1504-
},
1505-
))),
1506-
),
1507-
]
1508-
// WARN: This allows the non-conformant notation `_event.<PARAM>` in place of `_event.data.<PARAM>`
1509-
// for compatibility with the format produced by AS2FM.
1510-
// TODO: remove when not necessary anymore.
1511-
.into_iter()
1512-
.chain(params.iter().map(|(n, (v, t))| {
1513-
(
1514-
n.to_owned(),
1515-
EcmaObj::PrimitiveData(
1516-
Expression::Var(
1517-
v.clone(),
1518-
self.types
1519-
.get(t)
1520-
.map(|(_, t)| t.to_owned())
1521-
.expect("type of data parameter"),
1522-
),
1523-
t.to_owned(),
1524-
),
1525-
)
1526-
})),
1527-
))),
1570+
t.to_owned(),
1571+
),
1572+
)
1573+
},
1574+
))),
1575+
),
1576+
]))),
15281577
ident => {
15291578
let (var, type_name) = vars
15301579
.get(ident)

0 commit comments

Comments
 (0)