Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global variable mapping and preserve primitives #416

Merged
merged 22 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6121aa9
If old name is interchangeable, use instead
JSAbrahams Dec 30, 2022
fc91f5d
If expr of var declaration empty, ignore
JSAbrahams Jan 1, 2023
7444a10
Fix any_super and is_superset_of in Name
JSAbrahams Jan 2, 2023
a5d2acd
[ci skip] Simplify Expression try_from AST
JSAbrahams Jan 2, 2023
77e4444
[ci skip] Split up constraints for if else arms
JSAbrahams Jan 2, 2023
8007f51
[ci skip] Also gen then outside if
JSAbrahams Jan 2, 2023
42f7d23
Constraintbuilder deals with diverging paths
JSAbrahams Jan 3, 2023
8056c60
[ci skip] Remove old constr, prevent contamination
JSAbrahams Jan 3, 2023
e6d4039
Move variable mapping to constraint builder
JSAbrahams Jan 3, 2023
dde7108
[ci skip] Make variable mapping top-level
JSAbrahams Jan 3, 2023
69ce928
Add local variable mapping to environment
JSAbrahams Jan 3, 2023
befe23f
Fix off-by-one for exit set
JSAbrahams Jan 3, 2023
9c3b96a
Define self when entering class using new system
JSAbrahams Jan 3, 2023
c20cc1b
Ignore empty Name in push_ty
JSAbrahams Jan 3, 2023
ffbb235
Add test for shadowing in script, class, function
JSAbrahams Jan 3, 2023
ba0c2b0
Generate constraints for ast without access
JSAbrahams Jan 3, 2023
bc0cf86
Generate constraints for ast without access
JSAbrahams Jan 3, 2023
9ad7edf
Explicitly destroy mapping in Environment
JSAbrahams Jan 4, 2023
4d92a83
Only define self in functions, not class-level
JSAbrahams Jan 4, 2023
c582bf9
Use bitwise OR in Name is_superset_of method
JSAbrahams Jan 4, 2023
14b36a8
Remove redundant reference
JSAbrahams Jan 4, 2023
f497055
Use panic to denote bugs in the check stage
JSAbrahams Jan 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 71 additions & 49 deletions src/check/constrain/constraint/builder.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
use std::cmp::max;
use std::collections::HashMap;

use crate::check::constrain::constraint::Constraint;
use crate::check::constrain::constraint::expected::Expected;
use crate::check::constrain::constraint::iterator::Constraints;
use crate::check::name::string_name::StringName;
use crate::check::result::{TypeErr, TypeResult};
use crate::common::position::Position;
use crate::common::delimit::comma_delm;

pub type VarMapping = HashMap<String, usize>;

pub fn format_var_map(var: &str, offset: &usize) -> String {
if *offset == 0usize {
String::from(var)
} else {
format!("{var}@{offset}")
}
}

/// Constraint Builder.
///
Expand All @@ -14,80 +25,91 @@ use crate::common::position::Position;
///
/// The level indicates how deep we are. A level of 0 indicates that we are at
/// the top-level of a script.
///
/// We use sets to type check all possible execution paths.
/// We can have multiple sets open at a time.
/// When a constraint is added, we add it to each open path.
#[derive(Debug)]
pub struct ConstrBuilder {
pub level: usize,
finished: Vec<(Vec<StringName>, Vec<Constraint>)>,
constraints: Vec<(Vec<StringName>, Vec<Constraint>)>,
finished: Vec<Vec<Constraint>>,
constraints: Vec<Vec<Constraint>>,

pub var_mapping: VarMapping,
}

impl ConstrBuilder {
pub fn new() -> ConstrBuilder {
ConstrBuilder { level: 0, finished: vec![], constraints: vec![(vec![], vec![])] }
trace!("Created set at level {}", 0);
ConstrBuilder { finished: vec![], constraints: vec![vec![]], var_mapping: HashMap::new() }
}

pub fn is_top_level(&self) -> bool { self.level == 0 }
pub fn is_top_level(&self) -> bool { self.constraints.len() == 1 }

pub fn new_set_in_class(&mut self, inherit_class: bool, class: &StringName) {
self.new_set(true);
if self.level > 0 && inherit_class {
let mut previous = self.constraints[self.level - 1].0.clone();
self.constraints[self.level].0.append(&mut previous);
}
self.constraints[self.level].0.push(class.clone());
/// Insert variable for mapping in current constraint set.
///
/// This prevents shadowed variables from contaminating previous constraints.
///
/// Differs from environment since environment is used to check that variables are defined at a
/// certain location.
pub fn insert_var(&mut self, var: &str) {
let offset = self.var_mapping.get(var).map_or(0, |o| o + 1);
self.var_mapping.insert(String::from(var), offset);
}

/// Remove all constraints with where either parent or child is expected
pub fn remove_expected(&mut self, expected: &Expected) {
self.constraints[self.level].1 = self.constraints[self.level]
.1
.clone()
.drain_filter(|con| {
!con.left.expect.same_value(&expected.expect)
&& !con.right.expect.same_value(&expected.expect)
})
.collect()
}
/// Create new set, and create marker so that we know what set to exit to upon exit.
///
/// Output may also be ignored.
/// Useful if we don't want to close the set locally but leave open.
pub fn new_set(&mut self) -> usize {
let inherited_constraints = self.constraints.last().expect("Can never be empty");
self.constraints.push(inherited_constraints.clone());

pub fn new_set(&mut self, inherit: bool) {
self.constraints.push(if inherit {
(self.constraints[self.level].0.clone(), self.constraints[self.level].1.clone())
} else {
(vec![], vec![])
});
self.level += 1;
trace!("Created set at level {}", self.constraints.len() - 1);
self.constraints.len()
}

pub fn exit_set(&mut self, pos: Position) -> TypeResult<()> {
if self.level == 0 {
return Err(vec![TypeErr::new(pos, "Cannot exit top-level set")]);
/// Return to specified level given.
///
/// - Error if already top-level.
/// - Error if level greater than ceiling, as we cannot exit non-existent sets.
pub fn exit_set_to(&mut self, level: usize) {
let msg_exit = format!("Exit set to level {}", level - 1);

let level = max(1, level);
if level == 0 {
panic!("Cannot exit top-level set");
} else if level > self.constraints.len() {
panic!("Exiting constraint set which doesn't exist\nlevel: {}, constraints: {}, finished: {}",
level, self.constraints.len(), self.finished.len());
}

self.finished.push(self.constraints.remove(self.level));
self.level -= 1;
Ok(())
for i in (level - 1..self.constraints.len()).rev() {
// Equivalent to pop, but remove has better panic message for debugging
self.finished.push(self.constraints.remove(i))
}

trace!("{msg_exit}: {} active sets, {} complete sets", self.constraints.len(), self.finished.len());
}

/// Add new constraint to constraint builder with a message.
pub fn add(&mut self, msg: &str, parent: &Expected, child: &Expected) {
self.add_constr(&Constraint::new(msg, parent, child));
}

/// Add constraint to currently all op sets.
/// The open sets are the sets at levels between the self.level and active ceiling.
pub fn add_constr(&mut self, constraint: &Constraint) {
trace!("Constr[{}]: {} == {}, {}: {}", self.level, constraint.left.pos, constraint.right.pos, constraint.msg, constraint);
self.constraints[self.level].1.push(constraint.clone())
}
for constraints in &mut self.constraints {
constraints.push(constraint.clone());
}

pub fn current_class(&self) -> Option<StringName> {
let constraints = self.constraints[self.level].clone().0;
constraints.last().cloned()
let lvls = comma_delm(0..self.constraints.len());
trace!("Constr[{}]: {} == {}, {}: {}", lvls, constraint.left.pos, constraint.right.pos, constraint.msg, constraint);
}

// It is not redundant
#[allow(clippy::redundant_clone)]
pub fn all_constr(self) -> Vec<Constraints> {
let mut finished = self.finished.clone();
finished.append(&mut self.constraints.clone());
let (mut finished, mut constraints) = (self.finished, self.constraints);
finished.append(&mut constraints);
finished.iter().map(Constraints::from).collect()
}
}
29 changes: 11 additions & 18 deletions src/check/constrain/constraint/expected.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{Display, Error, Formatter};
Expand All @@ -7,8 +6,9 @@ use std::ops::Deref;

use itertools::{EitherOrBoth, Itertools};

use crate::check::constrain::constraint::builder::{format_var_map, VarMapping};
use crate::check::constrain::constraint::expected::Expect::*;
use crate::check::context::clss::{BOOL, FLOAT, INT, NONE, STRING};
use crate::check::context::clss::NONE;
use crate::check::name::{Any, Name, Nullable};
use crate::check::name::string_name::StringName;
use crate::check::result::{TypeErr, TypeResult};
Expand Down Expand Up @@ -40,13 +40,13 @@ impl AsRef<Expected> for Expected {
}
}

impl TryFrom<(&AST, &HashMap<String, String>)> for Expected {
impl TryFrom<(&AST, &VarMapping)> for Expected {
type Error = Vec<TypeErr>;

/// Creates Expected from AST.
///
/// If primitive or Constructor, constructs Type.
fn try_from((ast, mappings): (&AST, &HashMap<String, String>)) -> TypeResult<Expected> {
fn try_from((ast, mappings): (&AST, &VarMapping)) -> TypeResult<Expected> {
let ast = match &ast.node {
Node::Block { statements } => statements.last().unwrap_or(ast),
_ => ast,
Expand All @@ -56,10 +56,10 @@ impl TryFrom<(&AST, &HashMap<String, String>)> for Expected {
}
}

impl TryFrom<(&Box<AST>, &HashMap<String, String>)> for Expected {
impl TryFrom<(&Box<AST>, &VarMapping)> for Expected {
type Error = Vec<TypeErr>;

fn try_from((ast, mappings): (&Box<AST>, &HashMap<String, String>)) -> TypeResult<Expected> {
fn try_from((ast, mappings): (&Box<AST>, &VarMapping)) -> TypeResult<Expected> {
Expected::try_from((ast.deref(), mappings))
}
}
Expand All @@ -79,18 +79,18 @@ impl Any for Expect {
fn any() -> Self { Type { name: Name::any() } }
}

impl TryFrom<(&AST, &HashMap<String, String>)> for Expect {
impl TryFrom<(&AST, &VarMapping)> for Expect {
type Error = Vec<TypeErr>;

/// Also substitutes any identifiers with new ones from the environment if the environment
/// has a mapping.
/// This means that we forget about shadowed variables and continue with the new ones.
fn try_from((ast, mappings): (&AST, &HashMap<String, String>)) -> TypeResult<Expect> {
fn try_from((ast, mappings): (&AST, &VarMapping)) -> TypeResult<Expect> {
let ast = ast.map(&|node: &Node| {
if let Node::Id { lit } = node {
if let Some(name) = mappings.get(lit) {
if let Some(offset) = mappings.get(lit) {
// Always use name currently defined in environment
Node::Id { lit: name.clone() }
Node::Id { lit: format_var_map(lit, offset) }
} else {
node.clone()
}
Expand All @@ -99,14 +99,7 @@ impl TryFrom<(&AST, &HashMap<String, String>)> for Expect {
}
});

Ok(match &ast.node {
Node::Int { .. } | Node::ENum { .. } => Type { name: Name::from(INT) },
Node::Real { .. } => Type { name: Name::from(FLOAT) },
Node::Bool { .. } => Type { name: Name::from(BOOL) },
Node::Str { .. } => Type { name: Name::from(STRING) },
Node::Undefined => Expect::none(),
_ => Expression { ast },
})
Ok(Expression { ast })
}
}

Expand Down
10 changes: 4 additions & 6 deletions src/check/constrain/constraint/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@ use std::collections::VecDeque;

use crate::check::constrain::constraint::Constraint;
use crate::check::constrain::constraint::expected::Expected;
use crate::check::name::string_name::StringName;
use crate::check::result::{TypeErr, TypeResult};

#[derive(Clone, Debug)]
pub struct Constraints {
pub in_class: Vec<StringName>,
constraints: VecDeque<Constraint>,
}

impl From<&(Vec<StringName>, Vec<Constraint>)> for Constraints {
fn from((in_class, constraints): &(Vec<StringName>, Vec<Constraint>)) -> Self {
impl From<&Vec<Constraint>> for Constraints {
fn from(constraints: &Vec<Constraint>) -> Self {
let constraints = VecDeque::from(constraints.clone());
Constraints { in_class: in_class.clone(), constraints }
Constraints { constraints }
}
}

impl Constraints {
pub fn new() -> Constraints {
Constraints { in_class: Vec::new(), constraints: VecDeque::new() }
Constraints { constraints: VecDeque::new() }
}

pub fn len(&self) -> usize { self.constraints.len() }
Expand Down
Loading