diff --git a/vhdl_lang/src/analysis/expression.rs b/vhdl_lang/src/analysis/expression.rs index 3711c8942..5813c954c 100644 --- a/vhdl_lang/src/analysis/expression.rs +++ b/vhdl_lang/src/analysis/expression.rs @@ -1453,6 +1453,24 @@ mod test { ); } + #[test] + fn type_attributes_cannot_be_used_as_an_expression() { + let test = TestSetup::new(); + test.declarative_part("variable x : integer;"); + let code = test.snippet("x'subtype"); + + let mut diagnostics = Vec::new(); + assert_eq!(test.expr_type(&code, &mut diagnostics), None); + + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("x'subtype"), + "integer type 'INTEGER' cannot be used in an expression", + )], + ); + } + #[test] fn binary_expression_missing_names() { let test = TestSetup::new(); diff --git a/vhdl_lang/src/analysis/formal_region.rs b/vhdl_lang/src/analysis/formal_region.rs index 25cb19428..df6c29d0f 100644 --- a/vhdl_lang/src/analysis/formal_region.rs +++ b/vhdl_lang/src/analysis/formal_region.rs @@ -266,6 +266,8 @@ impl<'a> std::ops::Deref for GpkgInterfaceEnt<'a> { GpkgInterfaceEnt::Type(typ) => typ.deref(), GpkgInterfaceEnt::Constant(obj) => obj.deref(), GpkgInterfaceEnt::Subprogram(subp) => subp.deref(), + // `ent` is of type `&&AnyEnt`. `deref()` returns `&AnyEnt` which is what we want + #[allow(suspicious_double_ref_op)] GpkgInterfaceEnt::Package(ent) => ent.deref(), } } diff --git a/vhdl_lang/src/analysis/names.rs b/vhdl_lang/src/analysis/names.rs index ae6597878..12ef34bcb 100644 --- a/vhdl_lang/src/analysis/names.rs +++ b/vhdl_lang/src/analysis/names.rs @@ -316,7 +316,31 @@ impl<'a> ResolvedName<'a> { ); Err(EvalError::Unknown) } + + /// Convenience function that returns `Some(name)` when self is an object name, else `None` + fn as_object_name(&self) -> Option> { + match self { + ResolvedName::ObjectName(oname) => Some(*oname), + _ => None, + } + } +} + +/// Represents the result when resolving an attribute. +/// This can either be a value or a type. +/// +/// in the future, the value case might also hold the value +/// of the static expression of the attribute. +/// +/// Values are returned for attributes such as `'low`, `'high`, `'val(..)`, e.t.c. +/// Types are returned for attributes such as `'base`, `'subtype`, `'element` +pub enum AttrResolveResult<'a> { + /// The result type is a type. E.g. `a'base`, `a'subtype`, `a'element` + Type(BaseType<'a>), + /// The result type is a value with type, e.g. `a'low`, `b'high`, `c'image(x)` + Value(BaseType<'a>), } + #[derive(Debug)] pub struct AttributeSuffix<'a> { pub signature: &'a mut Option>, @@ -712,7 +736,7 @@ impl<'a> AnalyzeContext<'a> { prefix: &ResolvedName<'a>, attr: &mut AttributeSuffix, diagnostics: &mut dyn DiagnosticHandler, - ) -> EvalResult> { + ) -> EvalResult> { match attr.attr.item { AttributeDesignator::Left | AttributeDesignator::Right @@ -726,9 +750,10 @@ impl<'a> AnalyzeContext<'a> { attr.expr.as_mut().map(|expr| expr.as_mut()), diagnostics, ) + .map(AttrResolveResult::Value) } else if typ.is_scalar() { check_no_attr_argument(attr, diagnostics); - Ok(typ.into()) + Ok(AttrResolveResult::Value(typ.into())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -740,10 +765,10 @@ impl<'a> AnalyzeContext<'a> { let typ = prefix.as_type_of_attr_prefix(prefix_pos, attr, diagnostics)?; if typ.array_type().is_some() { - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } else if typ.is_scalar() { check_no_attr_argument(attr, diagnostics); - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -759,7 +784,7 @@ impl<'a> AnalyzeContext<'a> { } if typ.is_scalar() { - Ok(self.string().base()) + Ok(AttrResolveResult::Value(self.string().base())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -775,7 +800,7 @@ impl<'a> AnalyzeContext<'a> { } if typ.is_scalar() { - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -790,7 +815,7 @@ impl<'a> AnalyzeContext<'a> { if let Some(ref mut expr) = check_single_argument(name_pos, attr, diagnostics) { self.expr_with_ttyp(scope, typ, expr, diagnostics)?; } - Ok(self.universal_integer()) + Ok(AttrResolveResult::Value(self.universal_integer())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -805,7 +830,7 @@ impl<'a> AnalyzeContext<'a> { if let Some(ref mut expr) = check_single_argument(name_pos, attr, diagnostics) { self.integer_expr(scope, expr, diagnostics)?; } - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -823,7 +848,7 @@ impl<'a> AnalyzeContext<'a> { if let Some(ref mut expr) = check_single_argument(name_pos, attr, diagnostics) { self.expr_with_ttyp(scope, typ, expr, diagnostics)?; } - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -835,7 +860,7 @@ impl<'a> AnalyzeContext<'a> { let typ = prefix.as_type_of_attr_prefix(prefix_pos, attr, diagnostics)?; if typ.array_type().is_some() { - Ok(self.universal_integer()) + Ok(AttrResolveResult::Value(self.universal_integer())) } else { diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( name_pos, prefix, attr, @@ -847,7 +872,7 @@ impl<'a> AnalyzeContext<'a> { | AttributeDesignator::InstanceName | AttributeDesignator::PathName => { check_no_attr_argument(attr, diagnostics); - Ok(self.string().base()) + Ok(AttrResolveResult::Value(self.string().base())) } AttributeDesignator::Signal(sattr) => { @@ -858,45 +883,45 @@ impl<'a> AnalyzeContext<'a> { if let Some(expr) = attr.expr { self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; } - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } SignalAttribute::Stable | SignalAttribute::Quiet => { if let Some(expr) = expr { self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; } - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } SignalAttribute::Transaction => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.bit().base()) + Ok(AttrResolveResult::Value(self.bit().base())) } SignalAttribute::Event => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } SignalAttribute::Active => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } SignalAttribute::LastEvent => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.time().base()) + Ok(AttrResolveResult::Value(self.time().base())) } SignalAttribute::LastActive => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.time().base()) + Ok(AttrResolveResult::Value(self.time().base())) } SignalAttribute::LastValue => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } SignalAttribute::Driving => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(self.boolean().base()) + Ok(AttrResolveResult::Value(self.boolean().base())) } SignalAttribute::DrivingValue => { check_no_sattr_argument(sattr, expr, diagnostics); - Ok(typ.base()) + Ok(AttrResolveResult::Value(typ.base())) } } } @@ -912,9 +937,44 @@ impl<'a> AnalyzeContext<'a> { diagnostics.error(name_pos, "Range cannot be used as an expression"); Err(EvalError::Unknown) } - AttributeDesignator::Type(_) => { - diagnostics.error(name_pos, "Type cannot be used as an expression"); - Err(EvalError::Unknown) + AttributeDesignator::Type(attr) => self + .resolve_type_attribute_suffix(prefix, &attr, name_pos, diagnostics) + .map(|typ| AttrResolveResult::Type(typ.base())), + } + } + + /// Resolves any type attribute suffixes + /// + /// # Example + /// ```vhdl + /// variable x: std_logic_vector(2 downto 0); + /// x'subtype -> std_logic_vector(2 downto 0) + /// x'element -> std_logic + /// ``` + fn resolve_type_attribute_suffix( + &self, + prefix: &ResolvedName<'a>, + suffix: &TypeAttribute, + pos: &SrcPos, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + // all type attribute suffixes require that the prefix be an object type + let Some(obj) = prefix.as_object_name() else { + diagnostics.error(pos,format!("The {} attribute can only be used on objects, not {}", suffix, prefix.describe())); + return Err(EvalError::Unknown) + }; + match suffix { + TypeAttribute::Subtype => Ok(obj.type_mark()), + TypeAttribute::Element => { + if let Some((elem_type, _)) = obj.type_mark().array_type() { + Ok(elem_type) + } else { + diagnostics.error( + pos, + "The element attribute can only be used for array types", + ); + Err(EvalError::Unknown) + } } } } @@ -1035,13 +1095,15 @@ impl<'a> AnalyzeContext<'a> { } } - // Attributes for non-types not handled yet if let Suffix::Attribute(ref mut attr) = suffix { let typ = self.attribute_suffix(name_pos, &prefix.pos, scope, &resolved, attr, diagnostics)?; - return Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( - typ.into(), - ))); + return match typ { + AttrResolveResult::Type(base) => Ok(ResolvedName::Type(base.into())), + AttrResolveResult::Value(base) => Ok(ResolvedName::Expression( + DisambiguatedType::Unambiguous(base.into()), + )), + }; } match resolved { @@ -1737,6 +1799,90 @@ mod test { } } + #[test] + fn consecutive_name_attributes() { + let test = TestSetup::new(); + test.declarative_part( + "\ +variable a: natural range 0 to 9 := 9; +variable b: natural range 0 to a'subtype'high; + ", + ); + assert_matches!( + test.name_resolve(&test.snippet("b"), None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(_)) + ); + } + + #[test] + fn element_subtype_for_non_arrays() { + let test = TestSetup::new(); + test.declarative_part( + " +variable thevar : integer; + ", + ); + let code = test.snippet("thevar'element"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("thevar'element"), + "The element attribute can only be used for array types", + )], + ) + } + + #[test] + fn element_type_attributes_on_non_object_types() { + let test = TestSetup::new(); + test.declarative_part( + " +type my_type is array(natural range<>) of integer; + ", + ); + let code = test.snippet("my_type'subtype"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("my_type'subtype"), + "The subtype attribute can only be used on objects, not array type 'my_type'", + )], + ) + } + + #[test] + fn consecutive_type_attributes() { + let test = TestSetup::new(); + test.declarative_part( + " +variable x: integer; + ", + ); + let code = test.snippet("x'subtype'subtype'high"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("x'subtype'subtype"), + "The subtype attribute can only be used on objects, not integer type 'INTEGER'", + )], + ) + } + #[test] fn object_name() { let test = TestSetup::new(); @@ -2579,29 +2725,6 @@ variable thevar : integer_vector(0 to 1); ) } - #[test] - fn subtype_attribute() { - let test = TestSetup::new(); - test.declarative_part( - " -variable thevar : integer_vector(0 to 1); - ", - ); - let code = test.snippet("thevar'subtype"); - let mut diagnostics = Vec::new(); - assert_eq!( - test.name_resolve(&code, None, &mut diagnostics), - Err(EvalError::Unknown) - ); - check_diagnostics( - diagnostics, - vec![Diagnostic::error( - code, - "Type cannot be used as an expression", - )], - ) - } - #[test] fn name_attributes() { let test = TestSetup::new();