diff --git a/vhdl_lang/src/analysis/declarative.rs b/vhdl_lang/src/analysis/declarative.rs index 4f8adb15..ba2d198c 100644 --- a/vhdl_lang/src/analysis/declarative.rs +++ b/vhdl_lang/src/analysis/declarative.rs @@ -12,6 +12,7 @@ use crate::named_entity::{Signature, *}; use crate::{ast, named_entity, HasTokenSpan}; use analyze::*; use fnv::FnvHashMap; +use itertools::Itertools; use std::collections::hash_map::Entry; impl Declaration { @@ -54,7 +55,12 @@ impl Declaration { | Package(_) ), AnyEntKind::Design(Design::PackageBody | Design::UninstPackage(..)) - | AnyEntKind::Overloaded(Overloaded::SubprogramDecl(_) | Overloaded::Subprogram(_)) + | AnyEntKind::Overloaded( + Overloaded::SubprogramDecl(_) + | Overloaded::Subprogram(_) + | Overloaded::UninstSubprogramDecl(..) + | Overloaded::UninstSubprogram(..), + ) | AnyEntKind::Concurrent(Some(Concurrent::Process)) | AnyEntKind::Type(named_entity::Type::Protected(..)) => matches!( self, @@ -265,9 +271,12 @@ impl<'a> AnalyzeContext<'a> { } ResolvedName::Overloaded(des, overloaded) => { if let Some(ref mut signature) = signature { + // TODO: Uninstantiated subprogram in aliases match self.resolve_signature(scope, signature) { Ok(signature_key) => { - if let Some(ent) = overloaded.get(&signature_key) { + if let Some(ent) = + overloaded.get(&SubprogramKey::Normal(signature_key)) + { if let Some(reference) = name.item.suffix_reference_mut() { reference.set_unique_reference(&ent); } @@ -531,8 +540,40 @@ impl<'a> AnalyzeContext<'a> { } } } - Declaration::SubprogramInstantiation(_) => { - // TODO: subprogram instantiation statement + Declaration::SubprogramInstantiation(ref mut instance) => { + let subpgm_ent = self.arena.define( + &mut instance.ident, + parent, + AnyEntKind::Overloaded(Overloaded::Subprogram(Signature::new( + FormalRegion::new_params(), + None, + ))), + ); + let referenced_name = &mut instance.subprogram_name; + if let Some(name) = as_fatal(self.name_resolve( + scope, + &referenced_name.pos, + &mut referenced_name.item, + diagnostics, + ))? { + match self.generic_subprogram_instance( + scope, + &subpgm_ent, + &name, + instance, + diagnostics, + ) { + Ok(signature) => { + unsafe { + subpgm_ent.set_kind(AnyEntKind::Overloaded(Overloaded::Subprogram( + signature, + ))) + } + scope.add(subpgm_ent, diagnostics) + } + Err(err) => err.add_to(diagnostics)?, + } + } } Declaration::Use(ref mut use_clause) => { self.analyze_use_clause(scope, use_clause, diagnostics)?; @@ -562,6 +603,207 @@ impl<'a> AnalyzeContext<'a> { Ok(()) } + /// Analyze a generic subprogram instance, i.e., + /// ```vhdl + /// procedure my_proc is new my_proc generic map (T => std_logic); + /// ``` + /// + /// # Arguments + /// + /// * `scope` - The scope that this instance was declared in + /// * `inst_subprogram_ent` - A reference to the instantiated subprogram entity. + /// Used to set the parent reference of the signature + /// * `uninst_name` - The [ResolvedName] of the uninstantiated subprogram + /// * `instance` - A reference to the AST element of the subprogram instantiation + /// * `diagnostics` - The diagnostics handler + /// + /// # Returns + /// The signature after applying the optional map aspect of the uninstantiated subprogram + fn generic_subprogram_instance( + &self, + scope: &Scope<'a>, + inst_subprogram_ent: &EntRef<'a>, + uninst_name: &ResolvedName<'a>, + instance: &mut SubprogramInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> AnalysisResult { + let uninstantiated_subprogram = + self.resolve_uninstantiated_subprogram(scope, uninst_name, instance)?; + self.check_instantiated_subprogram_kind_matches_declared( + &uninstantiated_subprogram, + instance, + diagnostics, + ); + instance + .subprogram_name + .item + .set_unique_reference(&uninstantiated_subprogram); + let region = match uninstantiated_subprogram.kind() { + Overloaded::UninstSubprogramDecl(_, region) => region, + Overloaded::UninstSubprogram(_, region) => region, + _ => unreachable!(), + }; + + match as_fatal(self.generic_instance( + inst_subprogram_ent, + scope, + &instance.ident.tree.pos, + region, + &mut instance.generic_map, + diagnostics, + ))? { + None => Ok(uninstantiated_subprogram.signature().clone()), + Some((_, mapping)) => { + match self.map_signature( + Some(inst_subprogram_ent), + &mapping, + uninstantiated_subprogram.signature(), + ) { + Ok(signature) => Ok(signature), + Err(err) => { + let mut diag = Diagnostic::error(&instance.ident.tree.pos, err); + if let Some(pos) = uninstantiated_subprogram.decl_pos() { + diag.add_related(pos, "When instantiating this declaration"); + } + Err(AnalysisError::NotFatal(diag)) + } + } + } + } + } + + /// Given a `ResolvedName` and the subprogram instantiation, + /// find the uninstantiated subprogram that the resolved name references. + /// Return that resolved subprogram, if it exists, else return an `Err` + fn resolve_uninstantiated_subprogram( + &self, + scope: &Scope<'a>, + name: &ResolvedName<'a>, + instantiation: &mut SubprogramInstantiation, + ) -> AnalysisResult> { + let signature_key = match &mut instantiation.signature { + None => None, + Some(ref mut signature) => Some(( + self.resolve_signature(scope, signature)?, + signature.pos.clone(), + )), + }; + let overloaded_ent = match name { + ResolvedName::Overloaded(_, overloaded) => { + let choices = overloaded + .entities() + .filter(|ent| ent.is_uninst_subprogram()) + .collect_vec(); + if choices.is_empty() { + Err(AnalysisError::NotFatal(Diagnostic::error( + &instantiation.ident.tree.pos, + format!( + "{} does not denote an uninstantiated subprogram", + name.describe() + ), + ))) + } else if choices.len() == 1 { + // There is only one possible candidate + let ent = choices[0]; + // If the instantiated program has a signature, check that it matches + // that of the uninstantiated subprogram + if let Some((key, pos)) = signature_key { + match overloaded.get(&SubprogramKey::Uninstantiated(key)) { + None => Err(AnalysisError::NotFatal(Diagnostic::error( + pos.clone(), + format!( + "Signature does not match the the signature of {}", + ent.describe() + ), + ))), + Some(_) => Ok(ent), + } + } else { + Ok(ent) + } + } else if let Some((key, _)) = signature_key { + // There are multiple candidates + // but there is a signature that we can try to resolve + if let Some(resolved_ent) = + overloaded.get(&SubprogramKey::Uninstantiated(key.clone())) + { + Ok(resolved_ent) + } else { + Err(AnalysisError::NotFatal(Diagnostic::error( + &instantiation.subprogram_name.pos, + format!( + "No uninstantiated subprogram exists with signature {}", + key.describe() + ), + ))) + } + } else { + // There are multiple candidates + // and there is no signature to resolve + let mut err = Diagnostic::error( + &instantiation.subprogram_name.pos, + format!("Ambiguous instantiation of '{}'", overloaded.designator()), + ); + for ent in choices { + if let Some(pos) = &ent.decl_pos { + err.add_related(pos.clone(), format!("Might be {}", ent.describe())) + } + } + Err(AnalysisError::NotFatal(err)) + } + } + _ => Err(AnalysisError::NotFatal(Diagnostic::error( + &instantiation.subprogram_name.pos, + format!( + "{} does not denote an uninstantiated subprogram", + name.describe() + ), + ))), + }?; + if overloaded_ent.is_uninst_subprogram() { + Ok(overloaded_ent) + } else { + Err(AnalysisError::NotFatal(Diagnostic::error( + &instantiation.subprogram_name.pos, + format!("{} cannot be instantiated", overloaded_ent.describe()), + ))) + } + } + + /// Checks that an instantiated subprogram kind matches the declared subprogram. + /// For instance, when a subprogram was instantiated using + /// ```vhdl + /// function my_func is new proc; + /// ``` + /// where proc is + /// ```vhdl + /// procedure proc is + /// ... + /// ``` + /// + /// This function will push an appropriate diagnostic. + fn check_instantiated_subprogram_kind_matches_declared( + &self, + ent: &OverloadedEnt, + instance: &SubprogramInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) { + let err_msg = if ent.is_function() && instance.kind != SubprogramKind::Function { + Some("Instantiating function as procedure") + } else if ent.is_procedure() && instance.kind != SubprogramKind::Procedure { + Some("Instantiating procedure as function") + } else { + None + }; + if let Some(msg) = err_msg { + let mut err = Diagnostic::error(self.ctx.get_pos(instance.get_start_token()), msg); + if let Some(pos) = ent.decl_pos() { + err.add_related(pos, format!("{} declared here", ent.describe())); + } + diagnostics.push(err) + } + } + fn find_subpgm_specification( &self, scope: &Scope<'a>, @@ -571,7 +813,7 @@ impl<'a> AnalyzeContext<'a> { let des = decl.subpgm_designator().item.clone().into_designator(); if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) { - let ent = overloaded.get(&signature.key())?; + let ent = overloaded.get(&SubprogramKey::Normal(signature.key()))?; if ent.is_subprogram_decl() { return Some(ent); @@ -580,6 +822,32 @@ impl<'a> AnalyzeContext<'a> { None } + fn find_uninst_subpgm_specification( + &self, + scope: &Scope<'a>, + decl: &SubprogramSpecification, + signature: &Signature, + ) -> Option> { + let des = decl.subpgm_designator().item.clone().into_designator(); + + if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) { + // Note: This does not work in common circumstances with a generic type parameter + // since the parameters of the declared subprogram and the subprogram with body + // point to two different type-ID's. For example: + // function foo generic (type F); + // ^-- F has EntityId X + // function foo generic (type F) return F is ... end function foo; + // ^-- F has EntityId Y + // A future improvement must take this fact into account. + let ent = overloaded.get(&SubprogramKey::Uninstantiated(signature.key()))?; + + if ent.is_uninst_subprogram_decl() { + return Some(ent); + } + } + None + } + fn find_deferred_constant_declaration( &self, scope: &Scope<'a>, @@ -665,7 +933,9 @@ impl<'a> AnalyzeContext<'a> { if let Some(signature) = signature { match self.resolve_signature(scope, signature) { Ok(signature_key) => { - if let Some(ent) = overloaded.get(&signature_key) { + if let Some(ent) = + overloaded.get(&SubprogramKey::Normal(signature_key)) + { designator.set_unique_reference(&ent); ent.into() } else { @@ -1575,9 +1845,21 @@ impl<'a> AnalyzeContext<'a> { parent: EntRef<'a>, header: &mut SubprogramHeader, diagnostics: &mut dyn DiagnosticHandler, - ) -> FatalResult { - self.analyze_interface_list(scope, parent, &mut header.generic_list[..], diagnostics)?; - self.analyze_map_aspect(scope, &mut header.map_aspect, diagnostics) + ) -> FatalResult> { + let mut region = Region::default(); + for decl in header.generic_list.iter_mut() { + match self.analyze_interface_declaration(scope, parent, decl, diagnostics) { + Ok(ent) => { + region.add(ent, diagnostics); + scope.add(ent, diagnostics); + } + Err(err) => { + err.add_to(diagnostics)?; + } + } + } + self.analyze_map_aspect(scope, &mut header.map_aspect, diagnostics)?; + Ok(region) } fn subprogram_specification( @@ -1600,44 +1882,73 @@ impl<'a> AnalyzeContext<'a> { Some(&subprogram.subpgm_designator().pos), ); - let signature = match subprogram { + let (signature, generic_map) = match subprogram { SubprogramSpecification::Function(fun) => { - if let Some(header) = &mut fun.header { - self.subprogram_header(&subpgm_region, ent, header, diagnostics)?; - } + let generic_map = if let Some(header) = &mut fun.header { + Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?) + } else { + None + }; let params = self.analyze_parameter_list( &subpgm_region, ent, &mut fun.parameter_list, diagnostics, ); - let return_type = self.resolve_type_mark(scope, &mut fun.return_type); - Signature::new(params?, Some(return_type?)) + let return_type = self.resolve_type_mark(&subpgm_region, &mut fun.return_type); + (Signature::new(params?, Some(return_type?)), generic_map) } SubprogramSpecification::Procedure(procedure) => { - if let Some(header) = &mut procedure.header { - self.subprogram_header(&subpgm_region, ent, header, diagnostics)?; - } + let generic_map = if let Some(header) = &mut procedure.header { + Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?) + } else { + None + }; let params = self.analyze_parameter_list( &subpgm_region, ent, &mut procedure.parameter_list, diagnostics, ); - Signature::new(params?, None) + (Signature::new(params?, None), generic_map) } }; - let kind = to_kind(signature); + let mut kind = to_kind(signature); + if let Some(map) = generic_map { + match kind { + Overloaded::SubprogramDecl(signature) => { + kind = Overloaded::UninstSubprogramDecl(signature, map) + } + Overloaded::Subprogram(signature) => { + kind = Overloaded::UninstSubprogram(signature, map) + } + _ => unreachable!(), + } + } - if matches!(kind, Overloaded::Subprogram(_)) { - let declared_by = self.find_subpgm_specification(scope, subprogram, kind.signature()); + match kind { + Overloaded::Subprogram(_) => { + let declared_by = + self.find_subpgm_specification(scope, subprogram, kind.signature()); - if let Some(declared_by) = declared_by { - unsafe { - ent.set_declared_by(declared_by.into()); + if let Some(declared_by) = declared_by { + unsafe { + ent.set_declared_by(declared_by.into()); + } + } + } + Overloaded::UninstSubprogram(_, _) => { + let declared_by = + self.find_uninst_subpgm_specification(scope, subprogram, kind.signature()); + + if let Some(declared_by) = declared_by { + unsafe { + ent.set_declared_by(declared_by.into()); + } } } + _ => {} } unsafe { @@ -1714,6 +2025,8 @@ fn get_entity_class(ent: EntRef) -> Option { AnyEntKind::Overloaded(ent) => match ent { Overloaded::SubprogramDecl(s) | Overloaded::Subprogram(s) + | Overloaded::UninstSubprogramDecl(s, _) + | Overloaded::UninstSubprogram(s, _) | Overloaded::InterfaceSubprogram(s) => { if s.return_type.is_some() { Some(EntityClass::Function) diff --git a/vhdl_lang/src/analysis/overloaded.rs b/vhdl_lang/src/analysis/overloaded.rs index f3943fd9..ba64e842 100644 --- a/vhdl_lang/src/analysis/overloaded.rs +++ b/vhdl_lang/src/analysis/overloaded.rs @@ -286,6 +286,21 @@ impl<'a> AnalyzeContext<'a> { return Err(EvalError::Unknown); } + // Disambiguate based on uninstantiated subprogram + ok_kind.retain(|ent| !ent.is_uninst_subprogram()); + + if ok_kind.len() == 1 { + let ent = ok_kind[0]; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_kind.is_empty() { + diagnostics.push(Diagnostic::error( + call_name, + format!("uninstantiated subprogram {} cannot be called", call_name), + )); + return Err(EvalError::Unknown); + } + let ok_formals = self.disambiguate_by_assoc_formals(scope, call_pos, &ok_kind, assocs)?; // Only one candidate matched actual/formal profile diff --git a/vhdl_lang/src/analysis/package_instance.rs b/vhdl_lang/src/analysis/package_instance.rs index c0b45368..e628e3a6 100644 --- a/vhdl_lang/src/analysis/package_instance.rs +++ b/vhdl_lang/src/analysis/package_instance.rs @@ -5,24 +5,56 @@ //! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com use fnv::FnvHashMap; +use vhdl_lang::SrcPos; use super::analyze::*; use super::names::ResolvedName; use super::scope::*; -use crate::ast::ActualPart; use crate::ast::AssociationElement; use crate::ast::Expression; use crate::ast::Literal; use crate::ast::Name; use crate::ast::Operator; use crate::ast::PackageInstantiation; +use crate::ast::{ActualPart, MapAspect}; use crate::data::DiagnosticHandler; use crate::named_entity::*; use crate::Diagnostic; use crate::NullDiagnostics; impl<'a> AnalyzeContext<'a> { - fn package_generic_map( + pub fn generic_package_instance( + &self, + scope: &Scope<'a>, + package_ent: EntRef<'a>, + unit: &mut PackageInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let PackageInstantiation { + package_name, + generic_map, + .. + } = unit; + + match self.analyze_package_instance_name(scope, package_name) { + Ok(package_region) => self + .generic_instance( + package_ent, + scope, + &unit.ident.tree.pos, + package_region, + generic_map, + diagnostics, + ) + .map(|(region, _)| region), + Err(err) => { + diagnostics.push(err.into_non_fatal()?); + Err(EvalError::Unknown) + } + } + } + + pub fn generic_map( &self, scope: &Scope<'a>, generics: GpkgRegion<'a>, @@ -124,7 +156,7 @@ impl<'a> AnalyzeContext<'a> { let resolved = self.name_resolve(scope, &assoc.actual.pos, name, diagnostics)?; if let ResolvedName::Overloaded(des, overloaded) = resolved { - let signature = target.signature().key().map(|base_type| { + let signature = target.subprogram_key().map(|base_type| { mapping .get(&base_type.id()) .map(|ent| ent.base()) @@ -139,7 +171,7 @@ impl<'a> AnalyzeContext<'a> { "Cannot map '{}' to subprogram generic {}{}", des, target.designator(), - signature.describe() + signature.key().describe() ), ); @@ -188,62 +220,50 @@ impl<'a> AnalyzeContext<'a> { Ok(mapping) } - pub fn generic_package_instance( + pub fn generic_instance( &self, + ent: EntRef<'a>, scope: &Scope<'a>, - package_ent: EntRef<'a>, - unit: &mut PackageInstantiation, + decl_pos: &SrcPos, + uninst_region: &Region<'a>, + generic_map: &mut Option, diagnostics: &mut dyn DiagnosticHandler, - ) -> EvalResult> { - let PackageInstantiation { - package_name, - generic_map, - .. - } = unit; + ) -> EvalResult<(Region<'a>, FnvHashMap)> { + let nested = scope.nested().in_package_declaration(); + let (generics, other) = uninst_region.to_package_generic(); + + let mapping = if let Some(generic_map) = generic_map { + self.generic_map( + &nested, + generics, + generic_map.list.items.as_mut_slice(), + diagnostics, + )? + } else { + FnvHashMap::default() + }; - match self.analyze_package_instance_name(scope, package_name) { - Ok(package_region) => { - let nested = scope.nested().in_package_declaration(); - let (generics, other) = package_region.to_package_generic(); - - let mapping = if let Some(generic_map) = generic_map { - self.package_generic_map( - &nested, - generics, - generic_map.list.items.as_mut_slice(), - diagnostics, - )? - } else { - FnvHashMap::default() - }; - - for uninst in other { - match self.instantiate(Some(package_ent), &mapping, uninst) { - Ok(inst) => { - // We ignore diagnostics here, for example when adding implicit operators EQ and NE for interface types - // They can collide if there are more than one interface type that map to the same actual type - nested.add(inst, &mut NullDiagnostics); - } - Err(err) => { - let mut diag = Diagnostic::error(&unit.ident.tree.pos, err); - if let Some(pos) = uninst.decl_pos() { - diag.add_related(pos, "When instantiating this declaration"); - } - diagnostics.push(diag); - } + for uninst in other { + match self.instantiate(Some(ent), &mapping, uninst) { + Ok(inst) => { + // We ignore diagnostics here, for example when adding implicit operators EQ and NE for interface types + // They can collide if there are more than one interface type that map to the same actual type + nested.add(inst, &mut NullDiagnostics); + } + Err(err) => { + let mut diag = Diagnostic::error(decl_pos, err); + if let Some(pos) = uninst.decl_pos() { + diag.add_related(pos, "When instantiating this declaration"); } + diagnostics.push(diag); } - - Ok(nested.into_region()) - } - Err(err) => { - diagnostics.push(err.into_non_fatal()?); - Err(EvalError::Unknown) } } + + Ok((nested.into_region(), mapping)) } - fn instantiate( + pub(crate) fn instantiate( &self, parent: Option>, mapping: &FnvHashMap>, @@ -358,6 +378,16 @@ impl<'a> AnalyzeContext<'a> { Overloaded::Subprogram(signature) => { Overloaded::Subprogram(self.map_signature(parent, mapping, signature)?) } + Overloaded::UninstSubprogramDecl(signature, generic_map) => { + Overloaded::UninstSubprogramDecl( + self.map_signature(parent, mapping, signature)?, + generic_map.clone(), + ) + } + Overloaded::UninstSubprogram(signature, generic_map) => Overloaded::UninstSubprogram( + self.map_signature(parent, mapping, signature)?, + generic_map.clone(), + ), Overloaded::InterfaceSubprogram(signature) => { Overloaded::InterfaceSubprogram(self.map_signature(parent, mapping, signature)?) } @@ -379,7 +409,7 @@ impl<'a> AnalyzeContext<'a> { }) } - fn map_signature( + pub(crate) fn map_signature( &self, parent: Option>, mapping: &FnvHashMap>, diff --git a/vhdl_lang/src/analysis/semantic.rs b/vhdl_lang/src/analysis/semantic.rs index f3d5923f..55d6df23 100644 --- a/vhdl_lang/src/analysis/semantic.rs +++ b/vhdl_lang/src/analysis/semantic.rs @@ -195,6 +195,11 @@ impl<'a> AnalyzeContext<'a> { } } diagnostics.push(diagnostic); + } else if ent.is_uninst_subprogram_body() { + diagnostics.error( + &name.pos, + format!("uninstantiated {} cannot be called", ent.describe()), + ) } } None => {} diff --git a/vhdl_lang/src/analysis/standard.rs b/vhdl_lang/src/analysis/standard.rs index a020b594..17d8d1a3 100644 --- a/vhdl_lang/src/analysis/standard.rs +++ b/vhdl_lang/src/analysis/standard.rs @@ -326,7 +326,6 @@ impl<'a> AnalyzeContext<'a> { fn binary( &self, - op: Operator, implicit_of: TypeEnt<'a>, left: TypeEnt<'a>, diff --git a/vhdl_lang/src/analysis/tests/mod.rs b/vhdl_lang/src/analysis/tests/mod.rs index fe0d342b..e5ba78d3 100644 --- a/vhdl_lang/src/analysis/tests/mod.rs +++ b/vhdl_lang/src/analysis/tests/mod.rs @@ -23,6 +23,7 @@ mod resolves_names; mod resolves_type_mark; mod sensitivity_list; mod subprogram_arguments; +mod subprogram_instance; mod tool_directive; mod typecheck_expression; mod util; diff --git a/vhdl_lang/src/analysis/tests/subprogram_instance.rs b/vhdl_lang/src/analysis/tests/subprogram_instance.rs new file mode 100644 index 00000000..9a1c143c --- /dev/null +++ b/vhdl_lang/src/analysis/tests/subprogram_instance.rs @@ -0,0 +1,553 @@ +// 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/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; +use crate::syntax::test::check_diagnostics; +use crate::Diagnostic; + +#[test] +pub fn cannot_instantiate_procedure_that_does_not_exist() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure proc + generic ( x: natural := 1 ) is +begin +end proc; + +procedure proc is new foo; + ", + ); + + let diagnostics = builder.analyze(); + assert_eq!( + diagnostics, + vec![Diagnostic::error( + code.s1("foo").pos(), + "No declaration of 'foo'" + )] + ); +} + +#[test] +pub fn instantiate_wrong_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +signal x : bit; + +function proc is new x; + ", + ); + + let diagnostics = builder.analyze(); + assert_eq!( + diagnostics, + vec![Diagnostic::error( + code.s1("new x").s1("x"), + "signal 'x' does not denote an uninstantiated subprogram" + )] + ) +} + +#[test] +pub fn ambiguous_multiple_uninstantiated_subprograms() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : bit) +is begin +end foo; + +procedure foo + generic (type T) + parameter (x : bit; y: bit) +is begin +end foo; + +procedure proc is new foo; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("new foo").s1("foo"), + "Ambiguous instantiation of 'foo'", + ) + .related(code.s("foo", 1), "Might be procedure foo[BIT]") + .related(code.s("foo", 3), "Might be procedure foo[BIT, BIT]")], + ) +} + +#[test] +pub fn by_signature_resolved_multiple_uninstantiated_subprograms() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure foo + generic (a: natural) + parameter (x : bit) +is begin +end foo; + +procedure foo + generic (a: natural) + parameter (x : bit; y: bit) +is begin +end foo; + +procedure proc is new foo [bit] generic map (a => 5); +procedure proc2 is new foo [bit, bit] generic map (a => 5); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +pub fn complain_on_mismatching_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : bit) +is begin +end foo; + +procedure proc is new foo [bit, bit]; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::error( + code.s1("[bit, bit]").pos(), + "Signature does not match the the signature of procedure foo[BIT]", + )], + ); +} + +#[test] +pub fn can_instantiate_procedure_that_exists() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure proc + generic ( x: natural := 1 ) is +begin +end proc; + +procedure proc is new proc; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +pub fn instantiated_kind_vs_declared_kind() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure prok + generic ( x: natural := 1 ) is +begin +end prok; + +function func is new prok; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![ + Diagnostic::error(code.s1("function"), "Instantiating procedure as function") + .related(code.s1("prok"), "procedure prok[] declared here"), + ], + ); + + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function funk + generic ( x: natural := 1 ) return bit is +begin +end funk; + +procedure proc is new funk; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![ + Diagnostic::error(code.s1("procedure"), "Instantiating function as procedure") + .related(code.s1("funk"), "function funk[return BIT] declared here"), + ], + ); + + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function proc generic (type T) return bit is +begin +end proc; + +function proc is new proc; + ", + ); + + check_no_diagnostics(&builder.analyze()); + + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure proc generic (type T) is +begin +end proc; + +procedure proc is new proc; + ", + ); + + check_no_diagnostics(&builder.analyze()) +} + +#[test] +pub fn cannot_instantiate_procedure_without_header() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure proc is +begin +end proc; + +procedure proc is new proc; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::error( + code.s1("procedure proc is new").s("proc", 2).pos(), + "procedure proc[] does not denote an uninstantiated subprogram", + )], + ); +} + +#[test] +pub fn cannot_call_procedure_with_header() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure proc + generic ( x: natural := 1 ) + is + begin + end proc; +begin + proc; +end architecture arch; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::error( + code.s1("begin\n proc;").s1("proc").pos(), + "uninstantiated procedure proc[] cannot be called", + )], + ) +} + +#[test] +pub fn resolves_the_correct_instantiated_subprogram() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure proc + generic ( type T ) + is + begin + end proc; + + procedure proc is new proc generic map (T => natural); +begin + proc; +end architecture arch; + ", + ); + + check_no_diagnostics(&builder.analyze()) +} + +#[test] +pub fn resolves_its_generic_map() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure foo + generic ( type T ) + parameter (param : T) + is + begin + end foo; + + procedure foo is new foo generic map (T => natural); + procedure foo is new foo generic map (T => bit); +begin + foo('1'); + foo(42); +end architecture arch; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),), + Some( + code.s1("procedure foo is new foo generic map (T => bit)") + .s1("foo") + .pos() + ) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo(42)").s1("foo").end(),), + Some( + code.s1("procedure foo is new foo generic map (T => natural)") + .s1("foo") + .pos() + ) + ); + assert_eq!( + root.search_reference_pos( + code.source(), + code.s1("procedure foo is new foo generic map (T => natural)") + .s("foo", 2) + .end(), + ), + Some(code.s1("procedure foo").s1("foo").pos()) + ); + assert_eq!( + root.search_reference_pos( + code.source(), + code.s1("procedure foo is new foo generic map (T => bit)") + .s("foo", 2) + .end(), + ), + Some(code.s1("procedure foo").s1("foo").pos()) + ); +} + +#[test] +pub fn resolve_instantiated_function() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function foo + generic (type F) + parameter (x: F) +return F +is begin + return x; +end foo; + +function foo is new foo generic map (F => bit); + +constant x : bit := foo('1'); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),), + Some( + code.s1("function foo is new foo generic map (F => bit);") + .s1("foo") + .pos() + ) + ); +} + +#[test] +pub fn generic_function_declaration_with_separate_body() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function foo generic (type F) parameter (x: bit) return bit; + +function foo generic (type F) parameter (x: bit) return bit +is +begin + return x; +end foo; + +function foo is new foo generic map (F => natural); + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +// LRM 4.2.1: "An uninstantiated subprogram shall not be called, +// except as a recursive call within the body of the uninstantiated subprogram" +#[test] +#[ignore] +pub fn generic_subprogram_can_be_called_recursively() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function foo + generic (type F) + parameter (x: F) +return F +is begin + return foo(x); +end foo; + +procedure bar + generic (type F) + parameter (x: F) +is begin + bar(x); +end bar; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo(x)").s1("foo").end(),), + Some(code.s1("function foo").s1("foo").pos()) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("bar(x)").s1("bar").end(),), + Some(code.s1("procedure bar").s1("bar").pos()) + ); +} + +// LRM 4.2.1: "an uninstantiated subprogram shall not be used as a resolution +// function or used as a conversion function in an association list." +#[test] +#[ignore] +pub fn generic_subprogram_cannot_be_used_as_resolution_function() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function resolved generic (type F) parameter (x: F) return F; + +subtype x is resolved bit; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::error( + code.s1("subtype x is resolved bit").s1("resolved"), + "uninstantiated function resolved[F] return F cannot be used as resolution function", + )], + ); +} + +#[test] +#[ignore] +pub fn generic_subprogram_cannot_be_used_as_conversion_function() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure bar (x : in bit) is +begin +end bar; + +function foo generic (type F) parameter (x: bit) return bit; + ", + ); + + let code = builder.snippet("bar(foo('1'))"); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::error( + code.s1("foo"), + "uninstantiated function foo[F] return F cannot be used as conversion", + )], + ); +} + +#[test] +#[ignore] +pub fn generic_function_declaration_with_separate_body_and_type_parameters() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function foo generic (type F) parameter (x: F) return F; +function foo generic (type F) parameter (x: F) return F +is +begin + return x; +end foo; + +function foo is new foo generic map (F => std_logic); + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +#[test] +#[ignore] +pub fn by_signature_resolved_multiple_uninstantiated_subprograms_with_generics() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : T) +is begin +end foo; + +procedure foo + generic (type T) + parameter (x : T; y: T) +is begin +end foo; + +procedure proc is new foo [bit] generic map (T => bit); +procedure proc2 is new foo [bit, bit] generic map (T => bit); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} diff --git a/vhdl_lang/src/analysis/tests/tool_directive.rs b/vhdl_lang/src/analysis/tests/tool_directive.rs index debc1707..278fdd84 100644 --- a/vhdl_lang/src/analysis/tests/tool_directive.rs +++ b/vhdl_lang/src/analysis/tests/tool_directive.rs @@ -1,3 +1,8 @@ +// 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/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; #[test] diff --git a/vhdl_lang/src/ast.rs b/vhdl_lang/src/ast.rs index 0c17cd6b..bd6fa873 100644 --- a/vhdl_lang/src/ast.rs +++ b/vhdl_lang/src/ast.rs @@ -712,7 +712,7 @@ pub struct SubprogramHeader { pub map_aspect: Option, } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Copy)] pub enum SubprogramKind { Function, Procedure, diff --git a/vhdl_lang/src/named_entity.rs b/vhdl_lang/src/named_entity.rs index 92efb597..390a0947 100644 --- a/vhdl_lang/src/named_entity.rs +++ b/vhdl_lang/src/named_entity.rs @@ -19,7 +19,7 @@ use fnv::FnvHashMap; pub use types::{BaseType, Subtype, Type, TypeEnt, TypedSelection, UniversalType}; mod overloaded; -pub use overloaded::{Overloaded, OverloadedEnt, Signature, SignatureKey}; +pub use overloaded::{Overloaded, OverloadedEnt, Signature, SignatureKey, SubprogramKey}; mod object; pub use object::{Object, ObjectEnt, ObjectInterface}; @@ -284,6 +284,28 @@ impl<'a> AnyEnt<'a> { ) } + pub fn is_uninst_subprogram_decl(&self) -> bool { + matches!( + self.kind, + AnyEntKind::Overloaded(Overloaded::UninstSubprogramDecl(..)) + ) + } + + pub fn is_uninst_subprogram_body(&self) -> bool { + matches!( + self.kind(), + AnyEntKind::Overloaded(Overloaded::UninstSubprogram(..)) + ) + } + + pub fn is_uninst_subprogram(&self) -> bool { + matches!( + self.kind(), + AnyEntKind::Overloaded(Overloaded::UninstSubprogram(..)) + | AnyEntKind::Overloaded(Overloaded::UninstSubprogramDecl(..)) + ) + } + pub fn is_protected_type(&self) -> bool { matches!( self.kind, diff --git a/vhdl_lang/src/named_entity/overloaded.rs b/vhdl_lang/src/named_entity/overloaded.rs index d05578b0..7781269c 100644 --- a/vhdl_lang/src/named_entity/overloaded.rs +++ b/vhdl_lang/src/named_entity/overloaded.rs @@ -5,20 +5,43 @@ //! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com use super::*; use crate::ast::Designator; +use std::fmt::{Debug, Formatter}; pub enum Overloaded<'a> { SubprogramDecl(Signature<'a>), Subprogram(Signature<'a>), + UninstSubprogramDecl(Signature<'a>, Region<'a>), + UninstSubprogram(Signature<'a>, Region<'a>), InterfaceSubprogram(Signature<'a>), EnumLiteral(Signature<'a>), Alias(OverloadedEnt<'a>), } +impl<'a> Debug for Overloaded<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Overloaded::UninstSubprogramDecl(..) => { + write!(f, "uninstantiated subprogram declaration") + } + Overloaded::UninstSubprogram(..) => write!(f, "uninstantiated subprogram"), + Overloaded::SubprogramDecl(..) => write!(f, "subprogram declaration"), + Overloaded::Subprogram(..) => write!(f, "subprogram"), + Overloaded::InterfaceSubprogram(_) => write!(f, "interface subprogram"), + Overloaded::EnumLiteral(_) => write!(f, "enum literal"), + Overloaded::Alias(_) => write!(f, "alias"), + } + } +} + impl<'a> Overloaded<'a> { pub fn describe(&self) -> &'static str { use Overloaded::*; match self { - SubprogramDecl(signature) | Subprogram(signature) | InterfaceSubprogram(signature) => { + SubprogramDecl(signature) + | Subprogram(signature) + | UninstSubprogramDecl(signature, _) + | UninstSubprogram(signature, _) + | InterfaceSubprogram(signature) => { if signature.return_type().is_some() { "function" } else { @@ -35,6 +58,8 @@ impl<'a> Overloaded<'a> { Overloaded::InterfaceSubprogram(ref signature) | Overloaded::Subprogram(ref signature) | Overloaded::SubprogramDecl(ref signature) + | Overloaded::UninstSubprogram(ref signature, _) + | Overloaded::UninstSubprogramDecl(ref signature, _) | Overloaded::EnumLiteral(ref signature) => signature, Overloaded::Alias(ref overloaded) => overloaded.signature(), } @@ -139,6 +164,33 @@ pub struct SignatureKey<'a> { pub return_type: Option>, } +/// An Uninstantiated key is that of an uninstantiated subprogram +/// or an uninstantiated subprogram declaration. +/// Everything else is a Normal key. +/// This indirection is introduced because uninstantiated subprograms can have +/// the same signature as regular subprograms. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SubprogramKey<'a> { + Normal(SignatureKey<'a>), + Uninstantiated(SignatureKey<'a>), +} + +impl<'a> SubprogramKey<'a> { + pub fn map(self, map: impl Fn(BaseType<'a>) -> BaseType<'a>) -> Self { + match self { + SubprogramKey::Normal(sig) => SubprogramKey::Normal(sig.map(map)), + SubprogramKey::Uninstantiated(sig) => SubprogramKey::Uninstantiated(sig.map(map)), + } + } + + pub(crate) fn key(&self) -> &SignatureKey<'a> { + match self { + SubprogramKey::Normal(key) => key, + SubprogramKey::Uninstantiated(key) => key, + } + } +} + impl<'a> SignatureKey<'a> { pub fn new(formals: Vec>, return_type: Option>) -> SignatureKey<'a> { SignatureKey { @@ -181,6 +233,15 @@ impl<'a> OverloadedEnt<'a> { } } + pub fn subprogram_key(&self) -> SubprogramKey<'a> { + let key = self.signature().key(); + if self.is_uninst_subprogram() { + SubprogramKey::Uninstantiated(key) + } else { + SubprogramKey::Normal(key) + } + } + pub fn kind(&self) -> &'a Overloaded<'a> { if let AnyEntKind::Overloaded(kind) = self.ent.actual_kind() { kind @@ -223,6 +284,8 @@ impl<'a> OverloadedEnt<'a> { let prefix = match self.kind() { Overloaded::SubprogramDecl(_) | Overloaded::Subprogram(_) + | Overloaded::UninstSubprogramDecl(..) + | Overloaded::UninstSubprogram(..) | Overloaded::InterfaceSubprogram(_) => { if matches!(self.designator(), Designator::OperatorSymbol(_)) { "operator " diff --git a/vhdl_lang/src/named_entity/region.rs b/vhdl_lang/src/named_entity/region.rs index e2d96437..ee447cfb 100644 --- a/vhdl_lang/src/named_entity/region.rs +++ b/vhdl_lang/src/named_entity/region.rs @@ -6,6 +6,7 @@ use super::*; use crate::ast::*; +use crate::named_entity::overloaded::SubprogramKey; use fnv::FnvHashMap; use std::collections::hash_map::Entry; @@ -211,9 +212,9 @@ impl<'a> Region<'a> { } #[derive(Clone, Debug, PartialEq, Eq)] -/// A non-emtpy collection of overloaded entites +/// A non-empty collection of overloaded entities pub struct OverloadedName<'a> { - entities: FnvHashMap, OverloadedEnt<'a>>, + entities: FnvHashMap, OverloadedEnt<'a>>, } impl<'a> OverloadedName<'a> { @@ -221,14 +222,14 @@ impl<'a> OverloadedName<'a> { debug_assert!(!entities.is_empty()); let mut map = FnvHashMap::default(); for ent in entities.into_iter() { - map.insert(ent.signature().key(), ent); + map.insert(ent.subprogram_key(), ent); } OverloadedName { entities: map } } pub fn single(ent: OverloadedEnt) -> OverloadedName { let mut map = FnvHashMap::default(); - map.insert(ent.signature().key(), ent); + map.insert(ent.subprogram_key(), ent); OverloadedName { entities: map } } @@ -259,13 +260,13 @@ impl<'a> OverloadedName<'a> { self.entities().map(|ent| ent.signature()) } - pub fn get(&self, key: &SignatureKey) -> Option> { + pub fn get(&self, key: &SubprogramKey) -> Option> { self.entities.get(key).cloned() } #[allow(clippy::if_same_then_else)] fn insert(&mut self, ent: OverloadedEnt<'a>) -> Result<(), Diagnostic> { - match self.entities.entry(ent.signature().key()) { + match self.entities.entry(ent.subprogram_key()) { Entry::Occupied(mut entry) => { let old_ent = entry.get(); diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index 0b8d3eb4..846c444a 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -740,6 +740,8 @@ fn entity_kind_to_completion_kind(kind: &AnyEntKind) -> CompletionItemKind { AnyEntKind::Overloaded(overloaded) => match overloaded { Overloaded::SubprogramDecl(_) | Overloaded::Subprogram(_) + | Overloaded::UninstSubprogramDecl(..) + | Overloaded::UninstSubprogram(..) | Overloaded::InterfaceSubprogram(_) => CompletionItemKind::FUNCTION, Overloaded::EnumLiteral(_) => CompletionItemKind::ENUM_MEMBER, Overloaded::Alias(_) => CompletionItemKind::FIELD, @@ -914,6 +916,8 @@ fn overloaded_kind(overloaded: &Overloaded) -> SymbolKind { match overloaded { Overloaded::SubprogramDecl(_) => SymbolKind::FUNCTION, Overloaded::Subprogram(_) => SymbolKind::FUNCTION, + Overloaded::UninstSubprogramDecl(..) => SymbolKind::FUNCTION, + Overloaded::UninstSubprogram(..) => SymbolKind::FUNCTION, Overloaded::InterfaceSubprogram(_) => SymbolKind::FUNCTION, Overloaded::EnumLiteral(_) => SymbolKind::ENUM_MEMBER, Overloaded::Alias(o) => overloaded_kind(o.kind()),