rustre_core/
checks.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//! Collection of random checks that must be run for a program to be valid
//!
//! All of them should be directly or indirectly called by [`rustre_core::check`][crate::check()]

use crate::engine::Engine;
use crate::static_args::NodeInstance;
use crate::{Diagnostic, Level, Span};
use comemo::TrackedMut;
use rustre_parser::ast::{AstNode, AstToken, NodeNode, NodeProfileNode, ParamsNode};

/// Checks that the number of params and return params is strictly greater than 0
///
/// For some weird reason, Lustre imposes this restriction, not least in its very grammar. The
/// parser accepts an arity of zero, but the check needs to be done later for spec compliance.
#[memoize]
pub fn check_arity(mut engine: TrackedMut<Engine>, node: NodeNode) {
    // The standard library iterators are defined without any parameter, but they are a
    // special-cased lang item
    if crate::stdlib::is_iterator(TrackedMut::reborrow_mut(&mut engine), node.clone()) {
        return;
    }

    // TODO: create specific query for arity
    let sig = crate::get_signature(
        TrackedMut::reborrow_mut(&mut engine),
        NodeInstance::new_non_parametric(node.clone()),
    );

    let get_span = |f: fn(&NodeProfileNode) -> Option<ParamsNode>| {
        node.node_profile_node()
            .map(|profile| {
                f(&profile)
                    .map(|params| Span::of_node(params.syntax()))
                    .unwrap_or_else(|| Span::of_node(profile.syntax()))
            })
            .unwrap_or_else(|| Span::of_node(node.syntax()))
    };

    let node_kind_str = if node.is_function() {
        "function"
    } else {
        "node"
    };

    if sig.params.is_empty() {
        let name = sig.name.as_ref().map(|i| i.text()).unwrap_or_default();
        let span = get_span(NodeProfileNode::params);

        Diagnostic::build(Level::Error, format!("node {:?} takes no parameter", name))
            .with_attachment(span, "nodes must take at least 1 parameter")
            .with_help(format!("add a dummy parameter to this {node_kind_str}"))
            .emit(&mut engine);

        // TODO(examples): show the added parameter
    }

    if sig.return_params.is_empty() {
        let name = sig.name.as_ref().map(|i| i.text()).unwrap_or_default();
        let span = get_span(NodeProfileNode::return_params);

        Diagnostic::build(Level::Error, format!("node {:?} returns nothing", name))
            .with_attachment(span, "nodes must return at least 1 value")
            .with_help(format!("add a dummy parameter to this {node_kind_str}"))
            .emit(&mut engine);

        // TODO(examples): show the added return value
    }
}