Track down tricky context bug

This commit is contained in:
Jeff 2024-09-05 13:10:38 -04:00
parent 5c9a8dab31
commit c42fca496b
4 changed files with 94 additions and 42 deletions

View File

@ -84,6 +84,12 @@ impl<'a> Analyzer<'a> {
statement: &Statement, statement: &Statement,
context: &Context, context: &Context,
) -> Result<(), ContextError> { ) -> Result<(), ContextError> {
log::trace!(
"Analyzing statement {statement} at {:?} with context {}",
statement.position(),
context.id()
);
match statement { match statement {
Statement::Expression(expression) => self.analyze_expression(expression, context)?, Statement::Expression(expression) => self.analyze_expression(expression, context)?,
Statement::ExpressionNullified(expression_node) => { Statement::ExpressionNullified(expression_node) => {
@ -112,9 +118,7 @@ impl<'a> Analyzer<'a> {
}; };
if let Some(r#type) = r#type { if let Some(r#type) = r#type {
self.abstract_tree context.set_variable_type(identifier.inner.clone(), r#type.clone())?;
.context
.set_variable_type(identifier.inner.clone(), r#type.clone())?;
} else { } else {
self.errors self.errors
.push(AnalysisError::LetExpectedValueFromStatement { .push(AnalysisError::LetExpectedValueFromStatement {
@ -177,8 +181,9 @@ impl<'a> Analyzer<'a> {
context: &Context, context: &Context,
) -> Result<(), ContextError> { ) -> Result<(), ContextError> {
log::trace!( log::trace!(
"Analyzing expression {expression} at {:?}", "Analyzing expression {expression} at {:?} with context {}",
expression.position() expression.position(),
context.id()
); );
match expression { match expression {
@ -828,6 +833,8 @@ impl<'a> Analyzer<'a> {
BlockExpression::Sync(ast) => ast, BlockExpression::Sync(ast) => ast,
}; };
log::trace!("Analyzing block with context {}", ast.context.id());
for statement in &ast.statements { for statement in &ast.statements {
self.analyze_statement(statement, &ast.context)?; self.analyze_statement(statement, &ast.context)?;
} }

View File

@ -2,13 +2,22 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}, sync::{
atomic::{AtomicUsize, Ordering},
Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
},
}; };
use crate::{Constructor, Identifier, StructType, Type, Value}; use crate::{Constructor, Identifier, StructType, Type, Value};
pub type Associations = HashMap<Identifier, (ContextData, usize)>; pub type Associations = HashMap<Identifier, (ContextData, usize)>;
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn next_id() -> usize {
ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
/// Garbage-collecting context for variables. /// Garbage-collecting context for variables.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Context { pub struct Context {
@ -26,6 +35,7 @@ impl Context {
associations: RwLock::new(data), associations: RwLock::new(data),
parent: None, parent: None,
is_immutable: false, is_immutable: false,
id: next_id(),
}), }),
} }
} }
@ -36,6 +46,7 @@ impl Context {
associations: RwLock::new(data), associations: RwLock::new(data),
parent: None, parent: None,
is_immutable: true, is_immutable: true,
id: next_id(),
}), }),
} }
} }
@ -57,10 +68,15 @@ impl Context {
associations: RwLock::new(HashMap::new()), associations: RwLock::new(HashMap::new()),
parent: Some(Arc::downgrade(&self.inner)), parent: Some(Arc::downgrade(&self.inner)),
is_immutable: false, is_immutable: false,
id: next_id(),
}), }),
} }
} }
pub fn id(&self) -> usize {
self.inner.id
}
/// Returns the number of associated identifiers in the context. /// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> { pub fn association_count(&self) -> Result<usize, ContextError> {
self.inner.association_count() self.inner.association_count()
@ -183,6 +199,7 @@ impl Context {
associations: RwLock::new(new_associations), associations: RwLock::new(new_associations),
parent: None, parent: None,
is_immutable: false, is_immutable: false,
id: next_id(),
}); });
} }
} }
@ -195,12 +212,17 @@ impl Default for Context {
#[derive(Debug)] #[derive(Debug)]
pub struct ContextInner { pub struct ContextInner {
id: usize,
associations: RwLock<Associations>, associations: RwLock<Associations>,
parent: Option<Weak<ContextInner>>, parent: Option<Weak<ContextInner>>,
is_immutable: bool, is_immutable: bool,
} }
impl ContextInner { impl ContextInner {
fn parent(&self) -> Option<Arc<ContextInner>> {
self.parent.as_ref().and_then(|parent| parent.upgrade())
}
/// Returns the number of associated identifiers in the context. /// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> { pub fn association_count(&self) -> Result<usize, ContextError> {
Ok(self.associations.read()?.len()) Ok(self.associations.read()?.len())
@ -328,7 +350,7 @@ impl ContextInner {
Ok(None) Ok(None)
} }
/// Associates an identifier with a variable type, with a position given for garbage collection. /// Associates an identifier with a variable type.
pub fn set_variable_type( pub fn set_variable_type(
&self, &self,
identifier: Identifier, identifier: Identifier,
@ -338,7 +360,7 @@ impl ContextInner {
return Err(ContextError::CannotMutateImmutableContext); return Err(ContextError::CannotMutateImmutableContext);
} }
log::trace!("Setting {identifier} to type {type}."); log::trace!("Setting {identifier} to type {type} in context {}", self.id);
let mut associations = self.associations.write()?; let mut associations = self.associations.write()?;
let last_position = associations let last_position = associations
@ -364,7 +386,10 @@ impl ContextInner {
return Err(ContextError::CannotMutateImmutableContext); return Err(ContextError::CannotMutateImmutableContext);
} }
log::trace!("Setting {identifier} to value {value}"); log::trace!(
"Setting {identifier} to value {value} in context {}",
self.id
);
let mut associations = self.associations.write()?; let mut associations = self.associations.write()?;
let last_position = associations let last_position = associations
@ -390,7 +415,10 @@ impl ContextInner {
return Err(ContextError::CannotMutateImmutableContext); return Err(ContextError::CannotMutateImmutableContext);
} }
log::trace!("Setting {identifier} to constructor {constructor:?}"); log::trace!(
"Setting {identifier} to constructor {constructor:?} in context {}",
self.id
);
let mut associations = self.associations.write()?; let mut associations = self.associations.write()?;
let last_position = associations let last_position = associations
@ -417,7 +445,10 @@ impl ContextInner {
return Err(ContextError::CannotMutateImmutableContext); return Err(ContextError::CannotMutateImmutableContext);
} }
log::trace!("Setting {identifier} to constructor of type {struct_type}"); log::trace!(
"Setting {identifier} to constructor of type {struct_type} in context {}",
self.id
);
let mut variables = self.associations.write()?; let mut variables = self.associations.write()?;
let last_position = variables let last_position = variables
@ -435,7 +466,11 @@ impl ContextInner {
/// Collects garbage up to the given position, removing all variables with lesser positions. /// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> { pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
log::trace!("Collecting garbage up to {position}"); if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Collecting garbage up to {position} in context {}", self.id);
let mut variables = self.associations.write()?; let mut variables = self.associations.write()?;
@ -443,7 +478,7 @@ impl ContextInner {
let should_drop = position >= *last_used; let should_drop = position >= *last_used;
if should_drop { if should_drop {
log::trace!("Removing {identifier}"); log::trace!("Removing {identifier} from context {}", self.id);
} }
!should_drop !should_drop
@ -465,40 +500,60 @@ impl ContextInner {
let found = self.update_position_if_found(identifier, position)?; let found = self.update_position_if_found(identifier, position)?;
if found { if found {
Ok(true) return Ok(true);
}
let found_in_ancestor = if let Some(parent) = &self.parent() {
if parent.is_immutable {
false
} else { } else {
parent.update_position_if_found(identifier, position)?
}
} else {
false
};
if found_in_ancestor {
return Ok(true);
}
let mut associations = self.associations.write()?; let mut associations = self.associations.write()?;
log::trace!("Updating {identifier}'s last position to {position:?}"); log::trace!(
"Reserving {identifier} at position {position:?} in context {}",
self.id
);
associations.insert(identifier.clone(), (ContextData::Reserved, position)); associations.insert(identifier.clone(), (ContextData::Reserved, position));
Ok(false) Ok(false)
} }
}
fn update_position_if_found( fn update_position_if_found(
&self, &self,
identifier: &Identifier, identifier: &Identifier,
position: usize, position: usize,
) -> Result<bool, ContextError> { ) -> Result<bool, ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
let mut associations = self.associations.write()?; let mut associations = self.associations.write()?;
if let Some((_, last_position)) = associations.get_mut(identifier) { if let Some((_, last_position)) = associations.get_mut(identifier) {
log::trace!("Updating {identifier}'s last position to {position:?}"); log::trace!(
"Updating {identifier}'s last position to {position:?} in context {}",
self.id
);
*last_position = position; *last_position = position;
return Ok(true); Ok(true)
} else if let Some(parent) = &self.parent { } else {
if let Some(parent) = parent.upgrade() {
return parent.update_position_if_found(identifier, position);
}
}
Ok(false) Ok(false)
} }
} }
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ContextData { pub enum ContextData {

View File

@ -1020,10 +1020,7 @@ impl<'src> Parser<'src> {
let context = context.create_child(); let context = context.create_child();
log::trace!( log::trace!("Creating new block context {}", context.id());
"Creating new block context with {} associations",
context.association_count()?
);
let mut ast = AbstractSyntaxTree { let mut ast = AbstractSyntaxTree {
statements: VecDeque::new(), statements: VecDeque::new(),

View File

@ -1652,16 +1652,9 @@ mod tests {
assert_eq!(run(input), Ok(Some(Value::integer(42)))); assert_eq!(run(input), Ok(Some(Value::integer(42))));
} }
#[test]
fn built_in_function_dot_notation() {
let input = "42.to_string()";
assert_eq!(run(input), Ok(Some(Value::string("42"))));
}
#[test] #[test]
fn to_string() { fn to_string() {
let input = "to_string(42)"; let input = "42.to_string()";
assert_eq!(run(input), Ok(Some(Value::string("42")))); assert_eq!(run(input), Ok(Some(Value::string("42"))));
} }