Skip to content

Commit 867d9e9

Browse files
committed
Support type name as discrete range in aggregate choices #148
1 parent 7d4169c commit 867d9e9

File tree

4 files changed

+95
-28
lines changed

4 files changed

+95
-28
lines changed

vhdl_lang/src/analysis/expression.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -738,16 +738,33 @@ impl<'a> AnalyzeContext<'a> {
738738
for choice in choices.iter_mut() {
739739
match choice {
740740
Choice::Expression(index_expr) => {
741-
if let Some(index_type) = index_type {
742-
check.add(self.analyze_expression_with_target_type(
743-
scope,
744-
index_type,
745-
&index_expr.pos,
746-
&mut index_expr.item,
747-
diagnostics,
748-
)?);
741+
match self.expr_as_discrete_range_type(
742+
scope,
743+
&index_expr.pos,
744+
&mut index_expr.item,
745+
diagnostics,
746+
) {
747+
Ok(Some(_)) => {
748+
// @TODO check type matches index type
749+
can_be_array = true;
750+
}
751+
Ok(None) => {
752+
if let Some(index_type) = index_type {
753+
check.add(self.analyze_expression_with_target_type(
754+
scope,
755+
index_type,
756+
&index_expr.pos,
757+
&mut index_expr.item,
758+
diagnostics,
759+
)?);
760+
}
761+
can_be_array = false;
762+
}
763+
Err(err) => {
764+
diagnostics.push(err.into_non_fatal()?);
765+
return Ok(TypeCheck::Unknown);
766+
}
749767
}
750-
can_be_array = false;
751768
}
752769
Choice::DiscreteRange(ref mut drange) => {
753770
if let Some(index_type) = index_type {

vhdl_lang/src/analysis/names.rs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -428,35 +428,58 @@ impl<'a> AnalyzeContext<'a> {
428428
/// Example:
429429
/// subtype sub_t is natural range 0 to 1;
430430
/// arr(sub_t) := (others => 0);
431-
fn could_be_slice_with_type_name(
431+
fn assoc_as_discrete_range_type(
432432
&self,
433433
scope: &Scope<'a>,
434434
assocs: &mut [AssociationElement],
435435
diagnostics: &mut dyn DiagnosticHandler,
436-
) -> AnalysisResult<bool> {
436+
) -> AnalysisResult<Option<TypeEnt<'a>>> {
437437
if !could_be_indexed_name(assocs) {
438-
return Ok(false);
438+
return Ok(None);
439439
}
440440

441441
if let [ref mut assoc] = assocs {
442-
if let ActualPart::Expression(Expression::Name(name)) = &mut assoc.actual.item {
443-
let resolved = self.name_resolve(scope, &assoc.actual.pos, name, diagnostics)?;
442+
if let ActualPart::Expression(expr) = &mut assoc.actual.item {
443+
return self.expr_as_discrete_range_type(
444+
scope,
445+
&assoc.actual.pos,
446+
expr,
447+
diagnostics,
448+
);
449+
}
450+
}
451+
Ok(None)
452+
}
444453

445-
if let Some(ResolvedName::Type(typ)) = resolved {
446-
return if matches!(typ.base_type().kind(), Type::Enum { .. } | Type::Integer(_))
447-
{
448-
Ok(true)
449-
} else {
450-
Err(Diagnostic::error(
451-
&assoc.actual.pos,
452-
format!("{} cannot be used as a discrete range", typ.describe()),
453-
)
454-
.into())
455-
};
456-
}
454+
pub fn expr_as_discrete_range_type(
455+
&self,
456+
scope: &Scope<'a>,
457+
expr_pos: &SrcPos,
458+
expr: &mut Expression,
459+
diagnostics: &mut dyn DiagnosticHandler,
460+
) -> AnalysisResult<Option<TypeEnt<'a>>> {
461+
if let Expression::Name(name) = expr {
462+
if !name.is_selected_name() {
463+
// Early exit
464+
return Ok(None);
465+
}
466+
467+
let resolved = self.name_resolve(scope, expr_pos, name, diagnostics)?;
468+
469+
if let Some(ResolvedName::Type(typ)) = resolved {
470+
return if matches!(typ.base_type().kind(), Type::Enum { .. } | Type::Integer(_)) {
471+
Ok(Some(typ))
472+
} else {
473+
Err(Diagnostic::error(
474+
expr_pos,
475+
format!("{} cannot be used as a discrete range", typ.describe()),
476+
)
477+
.into())
478+
};
457479
}
458480
}
459-
Ok(false)
481+
482+
Ok(None)
460483
}
461484

462485
// Apply suffix when prefix is known to have a type
@@ -496,7 +519,10 @@ impl<'a> AnalyzeContext<'a> {
496519
// @TODO Prefix must non-overloaded
497520
Suffix::CallOrIndexed(assocs) => {
498521
if let Some(typ) = prefix_typ.sliced_as() {
499-
if self.could_be_slice_with_type_name(scope, assocs, diagnostics)? {
522+
if self
523+
.assoc_as_discrete_range_type(scope, assocs, diagnostics)?
524+
.is_some()
525+
{
500526
return Ok(Some(TypeOrMethod::Type(typ)));
501527
}
502528
}

vhdl_lang/src/analysis/tests/typecheck_expression.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,20 @@ constant good2 : string(1 to 6) := (\"text\", others => ' ');
913913
check_no_diagnostics(&diagnostics);
914914
}
915915

916+
#[test]
917+
fn array_element_association_may_be_type_denoting_discrete_range() {
918+
let mut builder = LibraryBuilder::new();
919+
builder.in_declarative_region(
920+
"
921+
subtype sub_t is natural range 1 to 3;
922+
constant good1 : integer_vector := (sub_t => 0);
923+
",
924+
);
925+
926+
let diagnostics = builder.analyze();
927+
check_no_diagnostics(&diagnostics);
928+
}
929+
916930
#[test]
917931
fn evaluates_unary_expressions() {
918932
let mut builder = LibraryBuilder::new();

vhdl_lang/src/ast/util.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ impl Name {
363363
Self::Slice(name, ..) => name.item.prefix(),
364364
}
365365
}
366+
367+
/// Returns true if the name is purely a selected name
368+
/// Example: a.b.c
369+
pub fn is_selected_name(&self) -> bool {
370+
match self {
371+
Name::Designator(_) => true,
372+
Name::Selected(prefix, _) => prefix.item.is_selected_name(),
373+
_ => false,
374+
}
375+
}
366376
}
367377

368378
impl CallOrIndexed {

0 commit comments

Comments
 (0)