Skip to content

Commit

Permalink
Add global variable mapping and preserve primitives (#416)
Browse files Browse the repository at this point in the history
* If old name is interchangeable, use instead

This means that old unions are preserved.
However, this doesn't quite explain to me why
we get rid of the unnecessary unions.
One would expect that we instead get even more
unnecessary unions.
As of now I can't quite yet explain this change
in behaviour, but should be documented once I
understand the change in behaviour.

* If expr of var declaration empty, ignore

Typically an indication that something somewhere
else went wrong.
However, an extra failsafe isn't too bad, though
it does negatively affect coverage if we fix the
problem elsewhere of course, though perhaps we're
treating the metric with that line of thought.

* Fix any_super and is_superset_of in Name

* Simplify Expression try_from AST

By treating all primitives as their type, we
are unable to properly substitute.
However, does cause some tests to fail which
relied on this behaviour:
If expressions, however, no longer appear to be
typed.

* Split up constraints for if else arms

If as a whole is now typed.
However, not when used, such as in function body
or variable definition.

* Also gen then outside if

This is to ensure that the constraints are
available outside the if.
For instance, when we want to substitute the if
with the then branch.
We don't use the environment, so we shouldn't
have any variable contamination.

* Constraintbuilder deals with diverging paths

We can now create multiple sets which are added to
in parallel for diverging paths.
The number of active sets is determined by the
difference between two internal unsigend numbers.

* Remove old constr, prevent contamination

We do now have cross contamination with variable
names.
Are not properly shadowed now.

Remove drain filter in ConstrBuilder
The method using this was used in a very specific
context in with.
If not reverted, can now compile with stable rust!

* Move variable mapping to constraint builder

* Make variable mapping top-level

If mapping of new var non-existent, add it to
mapping.

* Add local variable mapping to environment

This takes precedent, meaning that local shadows
are used.
If not found, then use global.
Useful if we have multiple execution paths and
later down the line need to perform substitutions.

* Fix off-by-one for exit set

* Define self when entering class using new system

As opposed to creating a new constraint.
When accessing self this should be dealt with
using the global variable mapping now.

* Ignore empty Name in push_ty

* Add test for shadowing in script, class, function

* Generate constraints for ast without access

- Simplify class logic in environment
- Add logic for self type in match_id.
- Remove logic for adding constraints for any
  potential field access of self.

* Explicitly destroy mapping in Environment

* Only define self in functions, not class-level

- Ignore type aliases explicitly for now
- Remove unnecessary logic in match_id
- Do actually shadow self, as self may de defined
  multiple times in sets inheriting from top-level
  set.

* Use panic to denote bugs in the check stage
  • Loading branch information
JSAbrahams committed Jan 4, 2023
1 parent 9723d5f commit 75a5ee1
Show file tree
Hide file tree
Showing 40 changed files with 500 additions and 448 deletions.
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

0 comments on commit 75a5ee1

Please sign in to comment.