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

Fixes #18125: Context should be hierarchic #3189

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 14 additions & 15 deletions rudder-lang/src/ast.rs
Expand Up @@ -11,7 +11,7 @@ use self::{context::VarContext, enums::EnumList, resource::*, value::Value, valu
use crate::ast::context::Type;
use crate::{error::*, parser::*};
use std::{
cmp::Ordering,
cmp::Ordering, rc::Rc,
collections::{HashMap, HashSet},
};

Expand All @@ -24,7 +24,7 @@ use std::{
pub struct AST<'src> {
errors: Vec<Error>,
// the context is used for variable lookup whereas variable_definitions is used for code generation
pub context: VarContext<'src>,
pub context: Rc<VarContext<'src>>,
pub enum_list: EnumList<'src>,
pub variable_definitions: HashMap<Token<'src>, Value<'src>>,
pub parameter_defaults: HashMap<(Token<'src>, Option<Token<'src>>), Vec<Option<Constant<'src>>>>, // also used as parameter list since that's all we have
Expand Down Expand Up @@ -85,7 +85,7 @@ impl<'src> AST<'src> {
fn new() -> AST<'src> {
AST {
errors: Vec::new(),
context: VarContext::new(),
context: Rc::new(VarContext::new(None)),
enum_list: EnumList::new(),
variable_definitions: HashMap::new(),
parameter_defaults: HashMap::new(),
Expand All @@ -96,11 +96,11 @@ impl<'src> AST<'src> {

/// Insert all initial enums
fn add_enums(&mut self, enums: Vec<PEnum<'src>>) {
let context = Rc::get_mut(&mut self.context).expect("Context has not been allocated before enums !");
for en in enums {
if en.global {
if let Err(e) = self
.context
.add_variable(None, en.name, Type::Enum(en.name))
if let Err(e) = context
.add_variable_declaration(en.name, Type::Enum(en.name))
{
self.errors.push(e);
}
Expand Down Expand Up @@ -172,27 +172,26 @@ impl<'src> AST<'src> {
/// Insert variables definition into the global context
/// Insert the variables definition into the global definition space
fn add_variables(&mut self, variables: Vec<PVariableDef<'src>>) {
let context = Rc::get_mut(&mut self.context).expect("Context has not been allocated before variables !");
for variable in variables {
let PVariableDef {
metadata,
name,
value,
} = variable;
let getter = |k| self.context.get(&k);
match Value::from_pvalue(&self.enum_list, &getter, value) {
match Value::from_pvalue(&self.enum_list, context, value) {
Err(e) => self.errors.push(e),
Ok(val) => {
if let Err(e) = self.context.add_variable(None, name, &val) {
self.errors.push(e);
}
self.variable_definitions.insert(variable.name, val);
Ok(val) => match context.add_variable_declaration(name, Type::from_value(&val)) {
Err(e) => self.errors.push(e),
Ok(()) => { self.variable_definitions.insert(name, val); }
}
}
}
}

/// Insert the variables declarations into the global context
fn add_magic_variables(&mut self, variables: Vec<PVariableDecl<'src>>) {
let context = Rc::get_mut(&mut self.context).expect("Context has not been allocated before magic variables !");
for variable in variables {
let PVariableDecl {
metadata,
Expand All @@ -202,7 +201,7 @@ impl<'src> AST<'src> {
} = variable;
match Type::from_ptype(type_, sub_elts) {
Ok(var_type) => {
if let Err(e) = self.context.add_variable(None, name, var_type) {
if let Err(e) = context.add_variable_declaration(name, var_type) {
self.errors.push(e);
}
}
Expand Down Expand Up @@ -329,7 +328,7 @@ impl<'src> AST<'src> {
res,
states,
res_children,
&self.context,
self.context.clone(),
&self.parameter_defaults,
&self.enum_list,
);
Expand Down
166 changes: 88 additions & 78 deletions rudder-lang/src/ast/context.rs
Expand Up @@ -5,6 +5,7 @@ use super::value::Value;
use super::value::Constant;
use crate::{error::*, parser::Token, parser::PType};
use std::collections::{hash_map, HashMap};
use std::rc::Rc;

/// Types some data can take
/// TODO: isn't this the same as a PType
Expand All @@ -18,9 +19,34 @@ pub enum Type<'src> {
Struct(HashMap<String, Type<'src>>), // Token instead of string ?
}

/// Implement conversion from value to type (a value already has a type)
impl<'src> From<&Value<'src>> for Type<'src> {
fn from(val: &Value<'src>) -> Self {
impl<'src> Type<'src> {
/// Create a type from parsed type
pub fn from_ptype(
type_: Option<PType<'src>>,
mut sub_elts: Vec<Token<'src>>,
) -> Result<Self> {
Ok(if sub_elts.len() == 0 {
match type_ {
None => Type::String, // default type is String
Some(PType::String) => Type::String,
Some(PType::Number) => Type::Number,
Some(PType::Boolean) => Type::Boolean,
Some(PType::Struct) => Type::Struct(HashMap::new()),
Some(PType::List) => Type::List,
_ => panic!("Phantom type should never be created !")
}
} else {
// this is a struct sub part
let first = sub_elts.remove(0);
let sub = Type::from_ptype(type_, sub_elts)?;
let mut map = HashMap::new();
map.insert(String::from(*first), sub);
Type::Struct(map)
})
}

/// Find the type of a given value
pub fn from_value(val: &Value<'src>) -> Self {
match val {
Value::String(_) => Type::String,
Value::Number(_, _) => Type::Number,
Expand All @@ -30,17 +56,15 @@ impl<'src> From<&Value<'src>> for Type<'src> {
Value::Struct(s) => {
let spec = s
.iter()
.map(|(k, v)| (k.clone(), v.into()))
.map(|(k, v)| (k.clone(), Type::from_value(v)))
.collect::<HashMap<String, Type<'src>>>();
Type::Struct(spec)
}
}
}
}

impl<'src> From<&Constant<'src>> for Type<'src> {
/// A constant necessarily has a type
fn from(val: &Constant<'src>) -> Self {
/// Find the type of a given constant
pub fn from_constant(val: &Constant<'src>) -> Self {
match val {
Constant::String(_, _) => Type::String,
Constant::Number(_, _) => Type::Number,
Expand All @@ -49,41 +73,14 @@ impl<'src> From<&Constant<'src>> for Type<'src> {
Constant::Struct(s) => {
let spec = s
.iter()
.map(|(k, v)| (k.clone(), v.into()))
.map(|(k, v)| (k.clone(), Type::from_constant(v)))
.collect::<HashMap<String, Type<'src>>>();
Type::Struct(spec)
}
}
}
}

impl<'src> Type<'src> {
/// Create a type from parsed type
pub fn from_ptype(
type_: Option<PType<'src>>,
mut sub_elts: Vec<Token<'src>>,
) -> Result<Self> {
Ok(if sub_elts.len() == 0 {
match type_ {
None => Type::String, // default type is String
Some(PType::String) => Type::String,
Some(PType::Number) => Type::Number,
Some(PType::Boolean) => Type::Boolean,
Some(PType::Struct) => Type::Struct(HashMap::new()),
Some(PType::List) => Type::List,
_ => panic!("Phantom type should never be created !")
}
} else {
// this is a struct sub part
let first = sub_elts.remove(0);
let sub = Type::from_ptype(type_, sub_elts)?;
let mut map = HashMap::new();
map.insert(String::from(*first), sub);
Type::Struct(map)
})
}
}

// TODO forbid variables names like global enum items (or enum type)

/// A context is a list of variables name with their type (and value if they are constant).
Expand All @@ -92,73 +89,90 @@ impl<'src> Type<'src> {
/// So this reference is asked by methods when they are needed.
#[derive(Debug, Clone)]
pub struct VarContext<'src> {
variables: HashMap<Token<'src>, Type<'src>>,
parent: Option<Rc<VarContext<'src>>>,
types: HashMap<Token<'src>, Type<'src>>,
definitions: HashMap<Token<'src>, Value<'src>>,
}

impl<'src> VarContext<'src> {
/// Constructor
pub fn new() -> VarContext<'static> {
pub fn new(parent: Option<Rc<VarContext>>) -> VarContext {
VarContext {
variables: HashMap::new(),
parent,
types: HashMap::new(),
definitions: HashMap::new(),
}
}

/// Returns the type of a given variable or None if variable doesn't exist
pub fn get(&self, key: &Token<'src>) -> Option<Type<'src>> {
pub fn get_type(&self, key: &Token<'src>) -> Option<Type<'src>> {
// clone should not be necessary, but i don't know how to handle lifetime hell without it
self.variables.get(key).map(Type::clone)
self.types
.get(key)
.map(Type::clone)
.or_else(
|| self.parent.clone()
.and_then(|p| p.get_type(key))
)
}

/// Iterator over all variables of this context.
pub fn iter(&self) -> hash_map::Iter<Token<'src>, Type<'src>> {
self.variables.iter()
self.types.iter()
}

/// Add a knew variable knowing its type (or its value which its convertible to type)
pub fn add_variable<T>(
fn must_not_exist(&self, name: &Token<'src>) -> Result<()> {
// disallow variable shadowing (TODO is that what we want ?)
if let Some(parent) = &self.parent {
parent.must_not_exist(name)?;
}
// disallow variable redefinition
if self.get_type(name).is_some() {
fail!(
name,
"Variable {} hides existing an variable {}",
name,
self.types.get_key_value(&name).unwrap().0
);
}
Ok(())
}

/// Add a knew variable knowing its type
pub fn add_variable_declaration(
&mut self,
upper_context: Option<&VarContext<'src>>, // TODO maybe we should not have an upper context and just clone the context when needed
name: Token<'src>,
type_value: T,
) -> Result<()>
where
T: Into<Type<'src>>,
{
type_: Type<'src>,
) -> Result<()> {
// disallow variable shadowing (TODO is that what we want ?)
if let Some(gc) = upper_context {
if gc.variables.contains_key(&name) {
fail!(
name,
"Variable {} hides global variable {}",
name,
gc.variables.get_key_value(&name).unwrap().0
);
}
if let Some(parent) = &self.parent {
parent.must_not_exist(&name)?;
}

// disallow variable redefinition except for struct which extends the structure
if self.variables.contains_key(&name) {
let current = self.variables.get_mut(&name).unwrap();
if self.types.contains_key(&name) {
let current = self.types.get_mut(&name).unwrap();
match current {
Type::Struct(desc) => match type_value.into() {
Type::Struct(desc) => match type_ {
Type::Struct(new_desc) => {
VarContext::extend_struct(name, desc, new_desc)?;
}
_ => fail!(
name,
"Variable {} extends a struct {} but is not a struct",
name,
self.variables.entry(name).key()
self.types.entry(name).key()
),
},
_ => fail!(
name,
"Variable {} redefines an existing variable {}",
name,
self.variables.entry(name).key()
self.types.entry(name).key()
),
}
} else {
self.variables.insert(name, type_value.into());
self.types.insert(name, type_);
}
Ok(())
}
Expand Down Expand Up @@ -196,32 +210,28 @@ mod tests {

#[test]
fn test_context() {
let mut context = VarContext::new();
let mut context = VarContext::new(None);
assert!(context
.add_variable(
None,
.add_variable_declaration(
pidentifier_t("var1"),
Type::Enum(pidentifier_t("enum1"))
)
.is_ok());
assert!(context
.add_variable(
None,
.add_variable_declaration(
pidentifier_t("var2"),
Type::Enum(pidentifier_t("enum1"))
)
.is_ok());
let mut c = VarContext::new();
let mut c = VarContext::new(Some(Rc::new(context)));
assert!(c
.add_variable(
Some(&context),
.add_variable_declaration(
pidentifier_t("var3"),
Type::Enum(pidentifier_t("enum2"))
)
.is_ok());
assert!(c
.add_variable(
Some(&context),
.add_variable_declaration(
pidentifier_t("var4"),
Type::Enum(pidentifier_t("enum1"))
)
Expand All @@ -238,10 +248,10 @@ mod tests {
type_,
} = pvariable_declaration_t(input);
let type_ = Type::from_ptype(type_, sub_elts).unwrap();
context.add_variable(None, name, type_)
context.add_variable_declaration(name, type_)
}

let mut context = VarContext::new();
let mut context = VarContext::new(None);

assert!(add_variable(&mut context, "let sys.windows").is_ok());
assert!(add_variable(&mut context, "let sys.windows").is_err()); // direct duplicate
Expand Down Expand Up @@ -271,6 +281,6 @@ mod tests {
"windows".into() => Type::String,
};

assert_eq!(context.variables[&"sys".into()], Type::Struct(os));
assert_eq!(context.types[&"sys".into()], Type::Struct(os));
}
}