From 85419c47beb50574e832da3a12d7fdec5e867582 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 13 Feb 2024 12:04:02 -0500 Subject: [PATCH] Convert maps to structures for advanced type checks --- src/abstract_tree/as.rs | 20 +++++++++++--------- src/abstract_tree/built_in_value.rs | 7 +++++-- src/abstract_tree/index.rs | 12 +++++++++++- src/error/runtime_error.rs | 8 +++++++- src/value/structure.rs | 16 +++++++++++++--- tests/as.rs | 20 +++++++++++++++++++- 6 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/abstract_tree/as.rs b/src/abstract_tree/as.rs index 444722c..210bc37 100644 --- a/src/abstract_tree/as.rs +++ b/src/abstract_tree/as.rs @@ -57,7 +57,11 @@ impl AbstractTree for As { }); } } - Type::Any => todo!(), + Type::Any => { + // Do no validation when converting from "any" to a list. + // This effectively defers to runtime behavior, potentially + // causing a runtime error. + } Type::Boolean => todo!(), Type::Collection => todo!(), Type::Custom(_) => todo!(), @@ -91,14 +95,12 @@ impl AbstractTree for As { Value::List(List::with_items(chars)) } - Value::Map(_) => todo!(), - Value::Function(_) => todo!(), - Value::Float(_) => todo!(), - Value::Integer(_) => todo!(), - Value::Boolean(_) => todo!(), - Value::Range(_) => todo!(), - Value::Option(_) => todo!(), - Value::Structure(_) => todo!(), + _ => { + return Err(RuntimeError::ConversionImpossible { + value, + target_type: self.r#type.clone(), + }); + } } } else { todo!() diff --git a/src/abstract_tree/built_in_value.rs b/src/abstract_tree/built_in_value.rs index 5bc4c35..a71114f 100644 --- a/src/abstract_tree/built_in_value.rs +++ b/src/abstract_tree/built_in_value.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::{ built_in_functions::{fs::fs_functions, json::json_functions, str::string_functions, Callable}, error::{RuntimeError, SyntaxError, ValidationError}, - AbstractTree, BuiltInFunction, Context, Format, Function, List, Map, SyntaxNode, Type, Value, + AbstractTree, BuiltInFunction, Context, Format, Function, List, Map, Structure, SyntaxNode, + Type, Value, }; static ARGS: OnceLock = OnceLock::new(); @@ -87,7 +88,9 @@ impl BuiltInValue { BuiltInValue::Args => Type::list(Type::String), BuiltInValue::AssertEqual => BuiltInFunction::AssertEqual.r#type(), BuiltInValue::Fs => Type::Map(None), - BuiltInValue::Json => Type::Map(None), + BuiltInValue::Json => Type::Map(Some( + Structure::from_map(self.get().as_map().unwrap()).unwrap(), + )), BuiltInValue::Length => BuiltInFunction::Length.r#type(), BuiltInValue::Output => BuiltInFunction::Output.r#type(), BuiltInValue::Random => Type::Map(None), diff --git a/src/abstract_tree/index.rs b/src/abstract_tree/index.rs index 853d153..a401235 100644 --- a/src/abstract_tree/index.rs +++ b/src/abstract_tree/index.rs @@ -48,7 +48,17 @@ impl AbstractTree for Index { fn expected_type(&self, context: &Context) -> Result { match self.collection.expected_type(context)? { Type::List(item_type) => Ok(*item_type.clone()), - Type::Map(_) => Ok(Type::Any), + Type::Map(structure) => { + if let Some(structure) = structure { + if let IndexExpression::Identifier(identifier) = &self.index { + if let Some((_, r#type)) = structure.inner().get(identifier.inner()) { + return Ok(r#type.clone()); + } + } + } + + Ok(Type::Any) + } Type::None => Ok(Type::None), r#type => Ok(r#type), } diff --git a/src/error/runtime_error.rs b/src/error/runtime_error.rs index 68eb221..94ff109 100644 --- a/src/error/runtime_error.rs +++ b/src/error/runtime_error.rs @@ -7,12 +7,18 @@ use std::{ time, }; -use crate::Value; +use crate::{Type, Value}; use super::rw_lock_error::RwLockError; #[derive(Debug, PartialEq)] pub enum RuntimeError { + /// The attempted conversion is impossible. + ConversionImpossible { + value: Value, + target_type: Type, + }, + Csv(String), Io(String), diff --git a/src/value/structure.rs b/src/value/structure.rs index 52bb835..0f13804 100644 --- a/src/value/structure.rs +++ b/src/value/structure.rs @@ -11,7 +11,7 @@ use serde::{ Deserialize, Deserializer, Serialize, Serializer, }; -use crate::{Type, Value}; +use crate::{error::rw_lock_error::RwLockError, Map, Type, Value}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Structure(Arc, Type)>>); @@ -21,6 +21,16 @@ impl Structure { Structure(Arc::new(map)) } + pub fn from_map(map: &Map) -> Result { + let mut structure = BTreeMap::new(); + + for (key, value) in map.inner()?.iter() { + structure.insert(key.clone(), (Some(value.clone()), value.r#type())); + } + + Ok(Structure(Arc::new(structure))) + } + pub fn inner(&self) -> &BTreeMap, Type)> { &self.0 } @@ -42,7 +52,7 @@ impl Display for Structure { } impl Serialize for Structure { - fn serialize(&self, serializer: S) -> std::result::Result + fn serialize(&self, serializer: S) -> Result where S: Serializer, { @@ -94,7 +104,7 @@ impl<'de> Visitor<'de> for StructureVisitor { } impl<'de> Deserialize<'de> for Structure { - fn deserialize(deserializer: D) -> std::result::Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { diff --git a/tests/as.rs b/tests/as.rs index 2a3cc0d..61add06 100644 --- a/tests/as.rs +++ b/tests/as.rs @@ -1,4 +1,7 @@ -use dust_lang::{error::ValidationError, *}; +use dust_lang::{ + error::{RuntimeError, ValidationError}, + *, +}; #[test] fn string_as_list() { @@ -25,3 +28,18 @@ fn string_as_list_conversion_error() { })) ) } + +const JSON: &str = "{ \"x\": 1 }"; + +#[test] +fn conversion_runtime_error() { + let json_value = interpret(&format!("json:parse('{JSON}')")).unwrap(); + + assert_eq!( + interpret(&format!("json:parse('{JSON}') as [map]")), + Err(Error::Runtime(RuntimeError::ConversionImpossible { + value: json_value, + target_type: Type::List(Box::new(Type::Float)) + })) + ) +}