/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! # Helpers for resolving UDL type expressions into concrete types. //! //! This module provides the [`TypeResolver`] trait, an abstraction for walking //! the parse tree of a weedle type expression and using a [`TypeCollector`] to //! convert it into a concrete type definition (so it assumes that you're already //! used a [`TypeFinder`](super::TypeFinder) to populate the universe). //! //! Perhaps most importantly, it knows how to error out if the UDL tries to reference //! an undefined or invalid type. use anyhow::{bail, Result}; use super::{Type, TypeCollector}; /// Trait to help resolving an UDL type node to a [`Type`]. /// /// This trait does structural matching against type-related weedle AST nodes from /// a parsed UDL file, turning them into a corresponding [`Type`] struct. It uses the /// known type definitions in a [`TypeCollector`] to resolve names to types. /// /// As a side-effect, resolving a type expression will grow the type universe with /// references to the types seen during traversal. For example resolving the type /// expression `sequence<?` will: /// /// * add `Optional` and `Sequence` to the /// known types in the universe. /// * error out if the type name `TestRecord` is not already known. /// pub(crate) trait TypeResolver { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result; } impl TypeResolver for &weedle::types::Type<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { (*self).resolve_type_expression(types) } } impl TypeResolver for weedle::types::Type<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { match self { weedle::types::Type::Single(t) => match t { weedle::types::SingleType::Any(_) => bail!("no support for `any` types"), weedle::types::SingleType::NonAny(t) => t.resolve_type_expression(types), }, weedle::types::Type::Union(_) => bail!("no support for union types yet"), } } } impl TypeResolver for weedle::types::NonAnyType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { match self { weedle::types::NonAnyType::Boolean(t) => t.resolve_type_expression(types), weedle::types::NonAnyType::Identifier(t) => t.resolve_type_expression(types), weedle::types::NonAnyType::Integer(t) => t.resolve_type_expression(types), weedle::types::NonAnyType::FloatingPoint(t) => t.resolve_type_expression(types), weedle::types::NonAnyType::Sequence(t) => t.resolve_type_expression(types), weedle::types::NonAnyType::RecordType(t) => t.resolve_type_expression(types), _ => bail!("no support for type {:?}", self), } } } impl TypeResolver for &weedle::types::AttributedNonAnyType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { if self.attributes.is_some() { bail!("type attributes are not supported yet"); } self.type_.resolve_type_expression(types) } } impl TypeResolver for &weedle::types::AttributedType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { if self.attributes.is_some() { bail!("type attributes are not supported yet"); } self.type_.resolve_type_expression(types) } } impl TypeResolver for weedle::types::MayBeNull { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { let type_ = self.type_.resolve_type_expression(types)?; match self.q_mark { None => Ok(type_), Some(_) => { let ty = Type::Optional { inner_type: Box::new(type_), }; Ok(ty) } } } } impl TypeResolver for weedle::types::IntegerType { fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result { bail!( "WebIDL integer types not implemented ({:?}); consider using u8, u16, u32 or u64", self ) } } impl TypeResolver for weedle::types::FloatingPointType { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { match self { weedle::types::FloatingPointType::Float(t) => t.resolve_type_expression(types), weedle::types::FloatingPointType::Double(t) => t.resolve_type_expression(types), } } } impl TypeResolver for weedle::types::SequenceType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { let t = self.generics.body.as_ref().resolve_type_expression(types)?; let ty = Type::Sequence { inner_type: Box::new(t), }; Ok(ty) } } impl TypeResolver for weedle::types::RecordKeyType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { use weedle::types::RecordKeyType::*; match self { Byte(_) | USV(_) => bail!( "WebIDL Byte or USV string type not implemented ({self:?}); \ consider using a string", ), DOM(_) => Ok(Type::String), NonAny(t) => t.resolve_type_expression(types), } } } impl TypeResolver for weedle::types::RecordType<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { let key_type = self.generics.body.0.resolve_type_expression(types)?; let value_type = self.generics.body.2.resolve_type_expression(types)?; let map = Type::Map { key_type: Box::new(key_type), value_type: Box::new(value_type), }; Ok(map) } } impl TypeResolver for weedle::common::Identifier<'_> { fn resolve_type_expression(&self, types: &mut TypeCollector) -> Result { match resolve_builtin_type(self.0) { Some(type_) => Ok(type_), None => match types.get_type_definition(self.0) { Some(type_) => Ok(type_), None => bail!("unknown type reference: {}", self.0), }, } } } impl TypeResolver for weedle::term::Boolean { fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result { Ok(Type::Boolean) } } impl TypeResolver for weedle::types::FloatType { fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result { if self.unrestricted.is_some() { bail!("we don't support `unrestricted float`"); } Ok(Type::Float32) } } impl TypeResolver for weedle::types::DoubleType { fn resolve_type_expression(&self, _types: &mut TypeCollector) -> Result { if self.unrestricted.is_some() { bail!("we don't support `unrestricted double`"); } Ok(Type::Float64) } } /// Resolve built-in API types by name. /// /// Given an identifier from the UDL, this will return `Some(Type)` if it names one of the /// built-in primitive types or `None` if it names something else. pub(crate) fn resolve_builtin_type(name: &str) -> Option { match name { "string" => Some(Type::String), "bytes" => Some(Type::Bytes), "u8" => Some(Type::UInt8), "i8" => Some(Type::Int8), "u16" => Some(Type::UInt16), "i16" => Some(Type::Int16), "u32" => Some(Type::UInt32), "i32" => Some(Type::Int32), "u64" => Some(Type::UInt64), "i64" => Some(Type::Int64), "f32" => Some(Type::Float32), "f64" => Some(Type::Float64), "timestamp" => Some(Type::Timestamp), "duration" => Some(Type::Duration), _ => None, } } #[cfg(test)] mod test { use super::*; use weedle::Parse; #[test] fn test_named_type_resolution() -> Result<()> { let mut types = TypeCollector::default(); types.add_type_definition( "TestRecord", Type::Record { name: "TestRecord".into(), module_path: "".into(), }, )?; assert_eq!(types.type_definitions.len(), 1); let (_, expr) = weedle::types::Type::parse("TestRecord").unwrap(); let t = types.resolve_type_expression(expr).unwrap(); assert!(matches!(t, Type::Record { name, .. } if name == "TestRecord")); assert_eq!(types.type_definitions.len(), 1); let (_, expr) = weedle::types::Type::parse("TestRecord?").unwrap(); let t = types.resolve_type_expression(expr).unwrap(); assert!(matches!(t, Type::Optional { .. })); assert!(match t { Type::Optional { inner_type } => matches!(*inner_type, Type::Record { name, .. } if name == "TestRecord"), _ => false, }); // assert_eq!(types.type_definitions.len(), 2); Ok(()) } #[test] fn test_error_on_unknown_type() -> Result<()> { let mut types = TypeCollector::default(); types.add_type_definition( "TestRecord", Type::Record { name: "TestRecord".into(), module_path: "".into(), }, )?; // Oh no, someone made a typo in the type-o... let (_, expr) = weedle::types::Type::parse("TestRecrd").unwrap(); let err = types.resolve_type_expression(expr).unwrap_err(); assert_eq!(err.to_string(), "unknown type reference: TestRecrd"); Ok(()) } #[test] fn test_error_on_union_type() -> Result<()> { let mut types = TypeCollector::default(); types.add_type_definition( "TestRecord", Type::Record { name: "TestRecord".into(), module_path: "".into(), }, )?; let (_, expr) = weedle::types::Type::parse("(TestRecord or u32)").unwrap(); let err = types.resolve_type_expression(expr).unwrap_err(); assert_eq!(err.to_string(), "no support for union types yet"); Ok(()) } }