From 776c34b250b96f4ad3f755b9052719d68ef0ff83 Mon Sep 17 00:00:00 2001 From: Andrei Tumbar Date: Tue, 20 Oct 2026 08:31:57 -0700 Subject: [PATCH] In-source template expansions --- .vscode/settings.json | 5 +- fpp_analysis/src/analysis.rs | 14 +- fpp_analysis/src/analyzers/module_analyzer.rs | 13 +- fpp_analysis/src/errors.rs | 42 +- fpp_analysis/src/lib.rs | 10 +- .../src/passes/check_template_defs.rs | 72 +++ .../src/passes/check_template_expansions.rs | 86 ++++ .../enter_template_expansion_symbols.rs | 17 +- .../passes/expand_syntax_and_enter_symbols.rs | 14 +- .../src/transform/expand_templates.rs | 116 ++--- .../tests/template/composition_invalid.fpp | 11 - .../template/composition_invalid.ref.txt | 14 - .../expand_before_invalid_composition.fpp | 6 +- .../expand_before_invalid_composition.ref.txt | 10 +- .../template/expand_before_invalid_nested.fpp | 5 +- .../expand_before_invalid_nested.ref.txt | 22 +- .../tests/template/expand_in_template_def.fpp | 9 + .../template/expand_in_template_def.ref.txt | 14 + .../tests/template/invalid_nested.fpp | 22 - .../tests/template/invalid_nested.ref.txt | 17 - fpp_analysis/tests/template/nested_expand.fpp | 9 + .../tests/template/nested_expand.ref.txt | 14 + .../tests/template/nested_template_def.fpp | 7 + .../template/nested_template_def.ref.txt | 16 + .../tests/template/source_expand_bad_args.fpp | 9 + .../template/source_expand_bad_args.ref.txt | 16 + .../tests/template/source_expand_include.fpp | 12 + .../tests/template/source_expand_include.fppi | 1 + .../template/source_expand_include.ref.txt | 8 + .../template/source_expand_member_use_ok.fpp | 28 ++ .../source_expand_member_use_ok.ref.txt | 0 .../tests/template/source_expand_ok.fpp | 9 + .../tests/template/source_expand_ok.ref.txt | 0 .../source_expand_undefined_symbol.fpp | 9 + .../source_expand_undefined_symbol.ref.txt | 7 + .../source_expand_undefined_template.fpp | 7 + .../source_expand_undefined_template.ref.txt | 7 + .../tests/template/template_def_in_expand.fpp | 11 + .../template/template_def_in_expand.ref.txt | 16 + fpp_analysis/tests/template/test.rs | 67 ++- fpp_ast/src/template.rs | 6 +- fpp_format/tests/formatter.rs | 1 + fpp_format/tests/template-expanded.fpp | 21 + fpp_format/tests/template-expanded.ref.fpp | 22 + fpp_lsp_parser/src/grammar/module.rs | 11 + .../include-module-template-expand.ref.txt | 33 ++ .../tests/include-module-template.ref.txt | 20 + fpp_lsp_parser/tests/integration.rs | 14 +- fpp_lsp_parser/tests/module-template.ref.txt | 218 +++++++++ fpp_lsp_parser/tests/parser_spec.rs | 40 ++ fpp_lsp_parser/tests/template.ref.txt | 138 ------ fpp_lsp_server/src/handlers.rs | 54 ++- fpp_parser/src/add_state_enums.rs | 5 + fpp_parser/src/parser.rs | 6 +- fpp_parser/src/parser/parser_spec.rs | 8 + fpp_parser/src/resolve_spec_include.rs | 16 + .../tests/include-module-template-expand.fpp | 9 + .../include-module-template-expand.ref.txt | 73 +++ fpp_parser/tests/include-module-template.fpp | 5 + .../tests/include-module-template.ref.txt | 49 ++ fpp_parser/tests/integration.rs | 40 +- .../{template.fpp => module-template.fpp} | 18 +- fpp_parser/tests/module-template.ref.txt | 450 ++++++++++++++++++ fpp_parser/tests/template.ref.txt | 296 ------------ fpp_python/python/fpp/__init__.pyi | 5 +- fpp_python/src/pipeline.rs | 8 +- fpp_query/README.md | 3 +- fpp_query/src/model.rs | 14 +- 68 files changed, 1661 insertions(+), 694 deletions(-) create mode 100644 fpp_analysis/src/passes/check_template_defs.rs create mode 100644 fpp_analysis/src/passes/check_template_expansions.rs delete mode 100644 fpp_analysis/tests/template/composition_invalid.fpp delete mode 100644 fpp_analysis/tests/template/composition_invalid.ref.txt create mode 100644 fpp_analysis/tests/template/expand_in_template_def.fpp create mode 100644 fpp_analysis/tests/template/expand_in_template_def.ref.txt delete mode 100644 fpp_analysis/tests/template/invalid_nested.fpp delete mode 100644 fpp_analysis/tests/template/invalid_nested.ref.txt create mode 100644 fpp_analysis/tests/template/nested_expand.fpp create mode 100644 fpp_analysis/tests/template/nested_expand.ref.txt create mode 100644 fpp_analysis/tests/template/nested_template_def.fpp create mode 100644 fpp_analysis/tests/template/nested_template_def.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_bad_args.fpp create mode 100644 fpp_analysis/tests/template/source_expand_bad_args.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_include.fpp create mode 100644 fpp_analysis/tests/template/source_expand_include.fppi create mode 100644 fpp_analysis/tests/template/source_expand_include.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_member_use_ok.fpp create mode 100644 fpp_analysis/tests/template/source_expand_member_use_ok.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_ok.fpp create mode 100644 fpp_analysis/tests/template/source_expand_ok.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_undefined_symbol.fpp create mode 100644 fpp_analysis/tests/template/source_expand_undefined_symbol.ref.txt create mode 100644 fpp_analysis/tests/template/source_expand_undefined_template.fpp create mode 100644 fpp_analysis/tests/template/source_expand_undefined_template.ref.txt create mode 100644 fpp_analysis/tests/template/template_def_in_expand.fpp create mode 100644 fpp_analysis/tests/template/template_def_in_expand.ref.txt create mode 100644 fpp_format/tests/template-expanded.fpp create mode 100644 fpp_format/tests/template-expanded.ref.fpp create mode 100644 fpp_lsp_parser/tests/include-module-template-expand.ref.txt create mode 100644 fpp_lsp_parser/tests/include-module-template.ref.txt create mode 100644 fpp_lsp_parser/tests/module-template.ref.txt delete mode 100644 fpp_lsp_parser/tests/template.ref.txt create mode 100644 fpp_parser/tests/include-module-template-expand.fpp create mode 100644 fpp_parser/tests/include-module-template-expand.ref.txt create mode 100644 fpp_parser/tests/include-module-template.fpp create mode 100644 fpp_parser/tests/include-module-template.ref.txt rename fpp_parser/tests/{template.fpp => module-template.fpp} (52%) create mode 100644 fpp_parser/tests/module-template.ref.txt delete mode 100644 fpp_parser/tests/template.ref.txt diff --git a/.vscode/settings.json b/.vscode/settings.json index f3acbb2d..8e06e93d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,10 +6,11 @@ "search.exclude": { "out": true // set this to false to include "out" folder in search results }, - "python-envs.defaultEnvManager": "ms-python.python:system", + "python-envs.defaultEnvManager": "ms-python.python:venv", "python.defaultInterpreterPath": "${workspaceFolder}/fpp_python/venv/bin/python", "python.analysis.extraPaths": [ - "${workspaceFolder}/fpp_python" + "${workspaceFolder}/fpp_python", + "./fpp_python/python" ], "python.testing.pytestArgs": [ "fpp_python" diff --git a/fpp_analysis/src/analysis.rs b/fpp_analysis/src/analysis.rs index 4bad826e..3450d53b 100644 --- a/fpp_analysis/src/analysis.rs +++ b/fpp_analysis/src/analysis.rs @@ -104,12 +104,16 @@ pub struct Analysis { pub(crate) tlm_packet_set: Option, /// The mapping from nodes to implied uses pub implied_use_map: HashMap, - /// The mapping from template expansion specifier nodes to their expansions, - /// in the order the expansions are entered - pub template_expansion_map: IndexMap, + /// Node of the current template definition + pub(crate) template_def_id: Option, /// The node of the template expansion specifier whose expansion we are /// currently analyzing inside of pub(crate) template_expansion: Option, + /// Node of the current template expansion specifier + pub(crate) template_expansion_id: Option, + /// The mapping from template expansion specifier nodes to their expansions, + /// in the order the expansions are entered + pub template_expansion_map: IndexMap, /// The set of symbols defined with a dictionary specifier pub dictionary_symbol_set: HashSet, /// The mapping from system symbols to systems @@ -174,8 +178,10 @@ impl Analysis { dictionary: None, tlm_packet_set: None, implied_use_map: Default::default(), - template_expansion_map: Default::default(), + template_def_id: None, template_expansion: None, + template_expansion_id: None, + template_expansion_map: Default::default(), dictionary_symbol_set: Default::default(), system_map: Default::default(), unknown_type: None, diff --git a/fpp_analysis/src/analyzers/module_analyzer.rs b/fpp_analysis/src/analyzers/module_analyzer.rs index 7e81507b..96427108 100644 --- a/fpp_analysis/src/analyzers/module_analyzer.rs +++ b/fpp_analysis/src/analyzers/module_analyzer.rs @@ -41,6 +41,11 @@ where /// names inside it resolve against the definitions the expansion introduced /// rather than same-named definitions at the expansion site. /// +/// The members are visited only once the analysis has recorded the expansion, +/// which [`crate::passes::EnterTemplateExpansionSymbols`] does. Before that, the +/// members of an expansion written in the source, in the form `expand T { ... }`, +/// have no expansion to resolve against, so they are not visited. +/// /// This is the analysis of a template expansion specifier by /// [`ModuleAnalyzer`]. A pass that visits module members without a /// [`ModuleAnalyzer`] calls it from [`Visitor::visit_spec_template_expand`]. @@ -52,11 +57,15 @@ pub fn visit_expansion<'ast, V>( where V: Visitor<'ast, State = Analysis>, { - // Check if this template has been expanded let Some(members) = &node.members else { return ControlFlow::Continue(()); }; - // Mark the analysis with the proper expansion + if !a.template_expansion_map.contains_key(&node.node_id) { + // This node has not yet been expanded by analysis + return ControlFlow::Continue(()); + } + // This node has been expanded by analysis. + // Mark the analysis with the proper expansion. let saved = a.template_expansion.replace(node.node_id); let out = members.visit(a, visitor); a.template_expansion = saved; diff --git a/fpp_analysis/src/errors.rs b/fpp_analysis/src/errors.rs index 789db0cd..1a5aeccc 100644 --- a/fpp_analysis/src/errors.rs +++ b/fpp_analysis/src/errors.rs @@ -402,15 +402,17 @@ pub enum SemanticError { param_loc: Span, def_loc: Span, }, - /// Nested template definition - NestedTemplateDefinition { - nested_loc: Span, - parent_loc: Span, + /// Error in a member of a module template definition + TemplateDefError { + loc: Span, + msg: String, + def_loc: Span, }, - /// Template expansion specifier nested inside a template - TemplateExpansionInTemplate { + /// Error in a member of a template expansion + TemplateExpandError { + loc: Span, + msg: String, expand_loc: Span, - template_loc: Span, }, /// Missing async input MissingAsync { @@ -1195,24 +1197,16 @@ impl From for Diagnostic { ), ) .span_note(def_loc, "conflicting definition is here"), - SemanticError::NestedTemplateDefinition { - nested_loc, - parent_loc, - } => Diagnostic::new( - nested_loc, - Level::Error, - "module template definitions cannot be nested", - ) - .span_note(parent_loc, "parent template defined here"), - SemanticError::TemplateExpansionInTemplate { - expand_loc, - template_loc, - } => Diagnostic::new( + SemanticError::TemplateDefError { loc, msg, def_loc } => { + Diagnostic::new(loc, Level::Error, msg) + .span_note(def_loc, "enclosing template is defined here") + } + SemanticError::TemplateExpandError { + loc, + msg, expand_loc, - Level::Error, - "template expansion specifiers cannot appear inside a module template", - ) - .span_note(template_loc, "enclosing template defined here"), + } => Diagnostic::new(loc, Level::Error, msg) + .span_note(expand_loc, "enclosing template expansion is here"), SemanticError::MissingAsync { kind, loc } => Diagnostic::new( loc, Level::Error, diff --git a/fpp_analysis/src/lib.rs b/fpp_analysis/src/lib.rs index 841c55f2..88c36d24 100644 --- a/fpp_analysis/src/lib.rs +++ b/fpp_analysis/src/lib.rs @@ -92,6 +92,12 @@ pub mod passes { mod check_uses_helpers; pub use check_uses_helpers::*; + mod check_template_defs; + pub use check_template_defs::*; + + mod check_template_expansions; + pub use check_template_expansions::*; + mod check_template_uses; pub use check_template_uses::*; @@ -336,8 +342,8 @@ pub fn resolve_includes( /// driver that resolves the includes and adds the state enums of each /// translation unit itself ([`resolve_includes`], [`add_state_enums`]). It /// runs before [`check_semantics`], on the same analysis, and it rewrites the -/// translation units, replacing each template expansion specifier with the -/// expanded template. A `Break` result means the caller must not go on to +/// translation units, replacing each unexpanded template expansion specifier +/// with the expanded template. A `Break` result means the caller must not go on to /// [`check_semantics`]. pub fn resolve_templates(a: &mut Analysis, tul: &mut [fpp_ast::TransUnit]) -> ControlFlow<()> { ExpandSyntaxAndEnterSymbols::enter_symbols_and_expand_templates(a, tul) diff --git a/fpp_analysis/src/passes/check_template_defs.rs b/fpp_analysis/src/passes/check_template_defs.rs new file mode 100644 index 00000000..ed75d0d7 --- /dev/null +++ b/fpp_analysis/src/passes/check_template_defs.rs @@ -0,0 +1,72 @@ +use crate::Analysis; +use crate::errors::SemanticError; +use fpp_ast::{ + DefModule, DefModuleTemplate, SpecTemplateExpand, TransUnit, Visitable, Visitor, Walkable, +}; +use fpp_core::Spanned; +use std::ops::ControlFlow; + +/// Check template definitions +/// +/// No module template definition may contain another module template definition +/// or a template expansion specifier, so expanding a template never defines +/// another template and never causes another expansion. +/// +/// The first error stops the pass: template expansion relies on these rules. +pub struct CheckTemplateDefs; + +impl<'ast> Visitor<'ast> for CheckTemplateDefs { + type Break = (); + type State = Analysis; + + fn visit_trans_unit(&self, a: &mut Analysis, node: &'ast TransUnit) -> ControlFlow<()> { + node.walk(a, self) + } + + fn visit_def_module(&self, a: &mut Analysis, def: &'ast DefModule) -> ControlFlow<()> { + def.walk(a, self) + } + + fn visit_def_module_template( + &self, + a: &mut Analysis, + def: &'ast DefModuleTemplate, + ) -> ControlFlow<()> { + if let Some(def_id) = a.template_def_id { + SemanticError::TemplateDefError { + loc: def.span(), + msg: "module template definitions cannot be nested".to_string(), + def_loc: def_id.span(), + } + .emit(); + return ControlFlow::Break(()); + } + a.template_def_id = Some(def.node_id); + let result = def + .members + .iter() + .try_for_each(|member| member.visit(a, self)); + a.template_def_id = None; + result + } + + fn visit_spec_template_expand( + &self, + a: &mut Analysis, + node: &'ast SpecTemplateExpand, + ) -> ControlFlow<()> { + match a.template_def_id { + Some(def_id) => { + SemanticError::TemplateDefError { + loc: node.span(), + msg: "template expansion specifiers cannot appear inside a module template" + .to_string(), + def_loc: def_id.span(), + } + .emit(); + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } +} diff --git a/fpp_analysis/src/passes/check_template_expansions.rs b/fpp_analysis/src/passes/check_template_expansions.rs new file mode 100644 index 00000000..c2f41693 --- /dev/null +++ b/fpp_analysis/src/passes/check_template_expansions.rs @@ -0,0 +1,86 @@ +use crate::Analysis; +use crate::errors::SemanticError; +use fpp_ast::{ + DefModule, DefModuleTemplate, SpecTemplateExpand, TransUnit, Visitable, Visitor, Walkable, +}; +use fpp_core::Spanned; +use std::ops::ControlFlow; + +/// Check template expansions +/// +/// The members of an expanded template, whether written in the source or produced +/// by template expansion, may contain no module template definition and no +/// template expansion specifier. The rule ensures that the members written in the +/// source could have been produced by expanding some template. +/// +/// The first error stops the pass: template expansion relies on these rules. +pub struct CheckTemplateExpansions; + +impl CheckTemplateExpansions { + fn spec_template_expand_unexpanded( + &self, + a: &mut Analysis, + node: &SpecTemplateExpand, + ) -> ControlFlow<()> { + match a.template_expansion_id { + Some(expand_id) => { + SemanticError::TemplateExpandError { + loc: node.span(), + msg: "template expansions cannot be nested".to_string(), + expand_loc: expand_id.span(), + } + .emit(); + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } +} + +impl<'ast> Visitor<'ast> for CheckTemplateExpansions { + type Break = (); + type State = Analysis; + + fn visit_trans_unit(&self, a: &mut Analysis, node: &'ast TransUnit) -> ControlFlow<()> { + node.walk(a, self) + } + + fn visit_def_module(&self, a: &mut Analysis, def: &'ast DefModule) -> ControlFlow<()> { + def.walk(a, self) + } + + fn visit_def_module_template( + &self, + a: &mut Analysis, + def: &'ast DefModuleTemplate, + ) -> ControlFlow<()> { + match a.template_expansion_id { + Some(expand_id) => { + SemanticError::TemplateExpandError { + loc: def.span(), + msg: "module template definitions cannot appear inside a template expansion" + .to_string(), + expand_loc: expand_id.span(), + } + .emit(); + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } + + fn visit_spec_template_expand( + &self, + a: &mut Analysis, + node: &'ast SpecTemplateExpand, + ) -> ControlFlow<()> { + self.spec_template_expand_unexpanded(a, node)?; + let Some(members) = &node.members else { + return ControlFlow::Continue(()); + }; + a.template_expansion_id = Some(node.node_id); + let result = members.iter().try_for_each(|member| member.visit(a, self)); + a.template_expansion_id = None; + result + } +} diff --git a/fpp_analysis/src/passes/enter_template_expansion_symbols.rs b/fpp_analysis/src/passes/enter_template_expansion_symbols.rs index aa21cd4c..8da512b7 100644 --- a/fpp_analysis/src/passes/enter_template_expansion_symbols.rs +++ b/fpp_analysis/src/passes/enter_template_expansion_symbols.rs @@ -53,6 +53,13 @@ impl<'ast> Visitor<'ast> for EnterTemplateExpansionSymbols { // resolve unexpanded, so there is nothing to enter return ControlFlow::Continue(()); }; + let Ok(Some(Symbol::Template(tmpl))) = a.get_template_symbol(node.template.id()) else { + // CheckTemplateUses reports a template name that does not resolve, + // and the analysis goes on past that error. The members of an + // expansion written in the source as `expand T { ... }` have no + // parameters to bind then, so there is nothing to enter. + return ControlFlow::Continue(()); + }; if a.template_expansion_map.contains_key(&node.node_id) { // We already entered this expansion @@ -65,7 +72,7 @@ impl<'ast> Visitor<'ast> for EnterTemplateExpansionSymbols { return result; } - if let Err(err) = self.enter_expansion(a, node, members) { + if let Err(err) = self.enter_expansion(a, &tmpl, node, members) { err.emit(); return ControlFlow::Break(()); } @@ -80,16 +87,12 @@ impl EnterTemplateExpansionSymbols { fn enter_expansion( &self, a: &mut Analysis, + tmpl: &Arc, node: &SpecTemplateExpand, members: &[ModuleMember], ) -> SemanticResult { - let tmpl = match a.get_template_symbol(node.template.id()) { - Ok(Some(Symbol::Template(tmpl))) => tmpl, - _ => panic!("template symbol should already be resolved"), - }; - // Build the parameter set of the expansion - let params = Self::bind_params(&tmpl, node)?; + let params = Self::bind_params(tmpl, node)?; // The parameter scope and the expansion scope are reached through the // expansion, so record it before filling either one in diff --git a/fpp_analysis/src/passes/expand_syntax_and_enter_symbols.rs b/fpp_analysis/src/passes/expand_syntax_and_enter_symbols.rs index 841e64a7..58bcb454 100644 --- a/fpp_analysis/src/passes/expand_syntax_and_enter_symbols.rs +++ b/fpp_analysis/src/passes/expand_syntax_and_enter_symbols.rs @@ -1,5 +1,8 @@ use crate::analysis::Analysis; -use crate::passes::{CheckTemplateUses, EnterSymbols, EnterTemplateExpansionSymbols}; +use crate::passes::{ + CheckTemplateDefs, CheckTemplateExpansions, CheckTemplateUses, EnterSymbols, + EnterTemplateExpansionSymbols, +}; use crate::transform::ExpandTemplates; use fpp_ast::{TransUnit, Visitor}; use fpp_core::{Error, FileReader, SourceFile}; @@ -26,9 +29,10 @@ impl ExpandSyntaxAndEnterSymbols { } /// The steps of [`ExpandSyntaxAndEnterSymbols::tu_list`] that follow include - /// resolution and the synthesis of the state enums: enter the symbols, match - /// each template expansion specifier to its template, expand the templates, - /// and enter the symbols the expansions introduce. + /// resolution and the synthesis of the state enums: enter the symbols, check + /// the template definitions and the expanded templates, match each template + /// expansion specifier to its template, expand the templates, and enter the + /// symbols the expansions introduce. /// /// The language server parses each file once and keeps the parsed unit, with /// its includes resolved and its state enums added, across analyses. Each @@ -38,6 +42,8 @@ impl ExpandSyntaxAndEnterSymbols { tul: &mut [TransUnit], ) -> ControlFlow<()> { EnterSymbols.visit_trans_units(a, tul.iter())?; + CheckTemplateDefs.visit_trans_units(a, tul.iter())?; + CheckTemplateExpansions.visit_trans_units(a, tul.iter())?; CheckTemplateUses.visit_trans_units(a, tul.iter())?; if let Err(err) = ExpandTemplates::transform_list(a, tul) { err.emit(); diff --git a/fpp_analysis/src/transform/expand_templates.rs b/fpp_analysis/src/transform/expand_templates.rs index b1ec092f..1d8c83d1 100644 --- a/fpp_analysis/src/transform/expand_templates.rs +++ b/fpp_analysis/src/transform/expand_templates.rs @@ -1,30 +1,28 @@ use crate::Analysis; -use crate::errors::{SemanticError, SemanticResult}; +use crate::errors::SemanticResult; use crate::semantics::Symbol; -use fpp_ast::{ - AstNode, DefModule, DefModuleTemplate, ModuleMember, Replicator, SpecTemplateExpand, TransUnit, -}; -use fpp_core::{Span, Spanned}; +use fpp_ast::{AstNode, ModuleMember, Replicator, SpecTemplateExpand, TransUnit}; +use fpp_core::Spanned; /// Expand any template expansion specifiers that have not yet been expanded /// -/// The specifier is replaced by the expanded template: the specifier itself, -/// carrying a copy of the syntax provided by the template definition it refers -/// to. That syntax behaves as if it were written at the point of the specifier, -/// except for the special rules for resolving names inside it, which are the -/// business of [`crate::passes::EnterTemplateExpansionSymbols`] and -/// [`crate::passes::CheckUses`]. +/// An unexpanded specifier is replaced by the expanded template: the specifier +/// itself, carrying a copy of the syntax provided by the template definition it +/// refers to. That syntax behaves as if it were written at the point of the +/// specifier, except for the special rules for resolving names inside it, which +/// are the business of [`crate::passes::EnterTemplateExpansionSymbols`] and +/// [`crate::passes::CheckUses`]. An expanded specifier, whether written in the +/// source as `expand T { ... }` or produced by an earlier expansion, is left +/// unchanged, so expanding a model twice is the same as expanding it once. /// /// Each copy gets its own node identities, so every expansion is a distinct set /// of definitions. The span of each copied node still points at the template /// body, with the specifier recorded as the point of expansion, so a diagnostic /// reports both. /// -/// The transform walks the model in source order, and it walks the body of a -/// template both where the template is defined and where it is expanded. The -/// `template_definition` parameter of the walk is the template definition the -/// walk is inside of, if any: the rules that a template body defines no -/// template and expands no template are checked against it. +/// [`crate::passes::CheckTemplateDefs`] has checked that no template body +/// defines a template or expands one, so a copy needs no further expansion, and +/// the transform does not visit template bodies. /// /// A model that fails this transform cannot be analyzed, so the first error /// stops the transform. @@ -41,77 +39,35 @@ impl ExpandTemplates { pub fn trans_unit(a: &Analysis, tu: &mut TransUnit) -> SemanticResult { for member in &mut tu.0 { - Self::match_module_member(a, None, member)?; + Self::match_module_member(a, member)?; } Ok(()) } - fn match_module_member( - a: &Analysis, - template_definition: Option, - member: &mut ModuleMember, - ) -> SemanticResult { + fn match_module_member(a: &Analysis, member: &mut ModuleMember) -> SemanticResult { match member { - ModuleMember::DefModule(def) => Self::def_module(a, template_definition, def), - ModuleMember::DefModuleTemplate(def) => { - Self::def_module_template(a, template_definition, def) - } - ModuleMember::SpecTemplateExpand(spec) => { - Self::spec_template_expand(a, template_definition, spec) + ModuleMember::DefModule(def) => { + for member in &mut def.members { + Self::match_module_member(a, member)?; + } + Ok(()) } + ModuleMember::SpecTemplateExpand(spec) => Self::spec_template_expand(a, spec), _ => Ok(()), } } - /// Check the rules that ensure expanding a template never defines another - /// template and never causes another expansion: no template definition may - /// be the ancestor of another template definition or of a template expansion - /// specifier. - fn def_module_template( - a: &Analysis, - template_definition: Option, - def: &mut DefModuleTemplate, - ) -> SemanticResult { - // Validate no nested template - if let Some(parent_loc) = template_definition { - return Err(SemanticError::NestedTemplateDefinition { - nested_loc: def.span(), - parent_loc, - }); - } - let template_definition = Some(def.span()); - for member in &mut def.members { - Self::match_module_member(a, template_definition, member)?; - } - Ok(()) - } - - fn def_module( - a: &Analysis, - template_definition: Option, - def: &mut DefModule, - ) -> SemanticResult { - for member in &mut def.members { - Self::match_module_member(a, template_definition, member)?; + fn spec_template_expand(a: &Analysis, spec: &mut SpecTemplateExpand) -> SemanticResult { + match spec.members { + // Members are already there; nothing to do + Some(_) => Ok(()), + // Members are not there; expand + None => Self::expand_template(a, spec), } - Ok(()) } - fn spec_template_expand( - a: &Analysis, - template_definition: Option, - spec: &mut SpecTemplateExpand, - ) -> SemanticResult { - if let Some(template_loc) = template_definition { - // The expansion specifier is nested inside a template body - return Err(SemanticError::TemplateExpansionInTemplate { - expand_loc: spec.span(), - template_loc, - }); - } - - // Not inside a template: expand it - + /// Expand a template + fn expand_template(a: &Analysis, spec: &mut SpecTemplateExpand) -> SemanticResult { // Look up the template def. CheckTemplateUses reports a template name // that does not resolve, and the analysis goes on past that error, so a // specifier whose name did not resolve is left unexpanded. @@ -119,16 +75,10 @@ impl ExpandTemplates { return Ok(()); }; - // Clone the template body, recording the template definition so that - // any nested template definition or expansion specifier is rejected. - let template_definition = Some(def.span()); + // Clone the template body, giving every cloned node its own identity and + // recording the expansion node so that cloned nodes point back at the + // expansion site let mut members = def.members.clone(); - for member in &mut members { - Self::match_module_member(a, template_definition, member)?; - } - - // Give every cloned node its own identity, recording the expansion node - // so that cloned nodes point back at the expansion site let replicator = Replicator::new(spec.span()); replicator.apply(&mut members); diff --git a/fpp_analysis/tests/template/composition_invalid.fpp b/fpp_analysis/tests/template/composition_invalid.fpp deleted file mode 100644 index a5c9e08c..00000000 --- a/fpp_analysis/tests/template/composition_invalid.fpp +++ /dev/null @@ -1,11 +0,0 @@ -module template T1( - constant p: U32 -) { - constant c = p -} - -module template T2( - constant p: U32 -) { - expand T1(constant p) -} diff --git a/fpp_analysis/tests/template/composition_invalid.ref.txt b/fpp_analysis/tests/template/composition_invalid.ref.txt deleted file mode 100644 index c7a30146..00000000 --- a/fpp_analysis/tests/template/composition_invalid.ref.txt +++ /dev/null @@ -1,14 +0,0 @@ - --> [ local path prefix ]/template/composition_invalid.fpp:10:5 - | -10 | expand T1(constant p) - | ^^^^^^^^^^^^^^^^^^^^^ template expansion specifiers cannot appear inside a module template - | - ::: [ local path prefix ]/template/composition_invalid.fpp:7:1 - | - 7 | / module template T2( - 8 | | constant p: U32 - 9 | | ) { -10 | | expand T1(constant p) -11 | | } - | |_- enclosing template defined here - diff --git a/fpp_analysis/tests/template/expand_before_invalid_composition.fpp b/fpp_analysis/tests/template/expand_before_invalid_composition.fpp index 480054a2..8ab80c40 100644 --- a/fpp_analysis/tests/template/expand_before_invalid_composition.fpp +++ b/fpp_analysis/tests/template/expand_before_invalid_composition.fpp @@ -1,6 +1,6 @@ -# The expansion of B comes first in the model, and the body of a template is -# checked where it is expanded, so the expansion specifier in B is the first -# error +# The expansion of B comes first in the model, but the template definitions are +# checked in source order before any template is expanded, so the expansion +# specifier in A is the first error expand B() diff --git a/fpp_analysis/tests/template/expand_before_invalid_composition.ref.txt b/fpp_analysis/tests/template/expand_before_invalid_composition.ref.txt index 452ee66f..d5cb7e05 100644 --- a/fpp_analysis/tests/template/expand_before_invalid_composition.ref.txt +++ b/fpp_analysis/tests/template/expand_before_invalid_composition.ref.txt @@ -1,10 +1,10 @@ - --> [ local path prefix ]/template/expand_before_invalid_composition.fpp:9:23 + --> [ local path prefix ]/template/expand_before_invalid_composition.fpp:7:23 | -9 | module template B() { expand Y() } +7 | module template A() { expand X() } | ^^^^^^^^^^ template expansion specifiers cannot appear inside a module template | - ::: [ local path prefix ]/template/expand_before_invalid_composition.fpp:9:1 + ::: [ local path prefix ]/template/expand_before_invalid_composition.fpp:7:1 | -9 | module template B() { expand Y() } - | ---------------------------------- enclosing template defined here +7 | module template A() { expand X() } + | ---------------------------------- enclosing template is defined here diff --git a/fpp_analysis/tests/template/expand_before_invalid_nested.fpp b/fpp_analysis/tests/template/expand_before_invalid_nested.fpp index df3c145a..da9aadb2 100644 --- a/fpp_analysis/tests/template/expand_before_invalid_nested.fpp +++ b/fpp_analysis/tests/template/expand_before_invalid_nested.fpp @@ -1,5 +1,6 @@ -# The expansion of B comes first in the model, and the body of a template is -# checked where it is expanded, so the nested template in B is the first error +# The expansion of B comes first in the model, but the template definitions are +# checked in source order before any template is expanded, so the nested +# template in A is the first error expand B() diff --git a/fpp_analysis/tests/template/expand_before_invalid_nested.ref.txt b/fpp_analysis/tests/template/expand_before_invalid_nested.ref.txt index 6b38a173..ee3a61f4 100644 --- a/fpp_analysis/tests/template/expand_before_invalid_nested.ref.txt +++ b/fpp_analysis/tests/template/expand_before_invalid_nested.ref.txt @@ -1,12 +1,12 @@ - --> [ local path prefix ]/template/expand_before_invalid_nested.fpp:11:3 - | -11 | module template B1() {} - | ^^^^^^^^^^^^^^^^^^^^^^^ module template definitions cannot be nested - | - ::: [ local path prefix ]/template/expand_before_invalid_nested.fpp:10:1 - | -10 | / module template B() { -11 | | module template B1() {} -12 | | } - | |_- parent template defined here + --> [ local path prefix ]/template/expand_before_invalid_nested.fpp:8:3 + | +8 | module template A1() {} + | ^^^^^^^^^^^^^^^^^^^^^^^ module template definitions cannot be nested + | + ::: [ local path prefix ]/template/expand_before_invalid_nested.fpp:7:1 + | +7 | / module template A() { +8 | | module template A1() {} +9 | | } + | |_- enclosing template is defined here diff --git a/fpp_analysis/tests/template/expand_in_template_def.fpp b/fpp_analysis/tests/template/expand_in_template_def.fpp new file mode 100644 index 00000000..54621c4d --- /dev/null +++ b/fpp_analysis/tests/template/expand_in_template_def.fpp @@ -0,0 +1,9 @@ +module template T1 { + +} + +module template T2 { + + expand T1 + +} diff --git a/fpp_analysis/tests/template/expand_in_template_def.ref.txt b/fpp_analysis/tests/template/expand_in_template_def.ref.txt new file mode 100644 index 00000000..79330b99 --- /dev/null +++ b/fpp_analysis/tests/template/expand_in_template_def.ref.txt @@ -0,0 +1,14 @@ + --> [ local path prefix ]/template/expand_in_template_def.fpp:7:3 + | +7 | expand T1 + | ^^^^^^^^^ template expansion specifiers cannot appear inside a module template + | + ::: [ local path prefix ]/template/expand_in_template_def.fpp:5:1 + | +5 | / module template T2 { +6 | | +7 | | expand T1 +8 | | +9 | | } + | |_- enclosing template is defined here + diff --git a/fpp_analysis/tests/template/invalid_nested.fpp b/fpp_analysis/tests/template/invalid_nested.fpp deleted file mode 100644 index e52774a6..00000000 --- a/fpp_analysis/tests/template/invalid_nested.fpp +++ /dev/null @@ -1,22 +0,0 @@ -module template Outer(constant c1: U32) { - module template Inner(constant c2: U32) { - constant k1 = c2 * 2 - } - - module M1 { - expand Inner(constant 10) - constant k2 = k1 * 3 - } - - module M2 { - expand Inner(constant 20) - constant k2 = k1 * 3 - } - - expand Inner(constant 30) - - constant j1 = M1.k1 + M2.k2 - constant j2 = k1 + M1.k1 + M2.k1 -} - -expand Outer(constant 10) diff --git a/fpp_analysis/tests/template/invalid_nested.ref.txt b/fpp_analysis/tests/template/invalid_nested.ref.txt deleted file mode 100644 index 8efa13e2..00000000 --- a/fpp_analysis/tests/template/invalid_nested.ref.txt +++ /dev/null @@ -1,17 +0,0 @@ - --> [ local path prefix ]/template/invalid_nested.fpp:2:5 - | - 2 | / module template Inner(constant c2: U32) { - 3 | | constant k1 = c2 * 2 - 4 | | } - | |_____^ module template definitions cannot be nested - | - ::: [ local path prefix ]/template/invalid_nested.fpp:1:1 - | - 1 | / module template Outer(constant c1: U32) { - 2 | | module template Inner(constant c2: U32) { - 3 | | constant k1 = c2 * 2 -... | -19 | | constant j2 = k1 + M1.k1 + M2.k1 -20 | | } - | |_- parent template defined here - diff --git a/fpp_analysis/tests/template/nested_expand.fpp b/fpp_analysis/tests/template/nested_expand.fpp new file mode 100644 index 00000000..a9e989c1 --- /dev/null +++ b/fpp_analysis/tests/template/nested_expand.fpp @@ -0,0 +1,9 @@ +module template T1 { + +} + +expand T1 { + + expand T1 + +} diff --git a/fpp_analysis/tests/template/nested_expand.ref.txt b/fpp_analysis/tests/template/nested_expand.ref.txt new file mode 100644 index 00000000..68059026 --- /dev/null +++ b/fpp_analysis/tests/template/nested_expand.ref.txt @@ -0,0 +1,14 @@ + --> [ local path prefix ]/template/nested_expand.fpp:7:3 + | +7 | expand T1 + | ^^^^^^^^^ template expansions cannot be nested + | + ::: [ local path prefix ]/template/nested_expand.fpp:5:1 + | +5 | / expand T1 { +6 | | +7 | | expand T1 +8 | | +9 | | } + | |_- enclosing template expansion is here + diff --git a/fpp_analysis/tests/template/nested_template_def.fpp b/fpp_analysis/tests/template/nested_template_def.fpp new file mode 100644 index 00000000..0b47b127 --- /dev/null +++ b/fpp_analysis/tests/template/nested_template_def.fpp @@ -0,0 +1,7 @@ +module template T1 { + + module template T2 { + + } + +} diff --git a/fpp_analysis/tests/template/nested_template_def.ref.txt b/fpp_analysis/tests/template/nested_template_def.ref.txt new file mode 100644 index 00000000..917453d6 --- /dev/null +++ b/fpp_analysis/tests/template/nested_template_def.ref.txt @@ -0,0 +1,16 @@ + --> [ local path prefix ]/template/nested_template_def.fpp:3:5 + | +3 | / module template T2 { +4 | | +5 | | } + | |_____^ module template definitions cannot be nested + | + ::: [ local path prefix ]/template/nested_template_def.fpp:1:1 + | +1 | / module template T1 { +2 | | +3 | | module template T2 { +... | +7 | | } + | |_- enclosing template is defined here + diff --git a/fpp_analysis/tests/template/source_expand_bad_args.fpp b/fpp_analysis/tests/template/source_expand_bad_args.fpp new file mode 100644 index 00000000..796116e6 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_bad_args.fpp @@ -0,0 +1,9 @@ +module template T { + +} + +expand T(constant 10) { + + constant a = 0 + +} diff --git a/fpp_analysis/tests/template/source_expand_bad_args.ref.txt b/fpp_analysis/tests/template/source_expand_bad_args.ref.txt new file mode 100644 index 00000000..e256e947 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_bad_args.ref.txt @@ -0,0 +1,16 @@ + --> [ local path prefix ]/template/source_expand_bad_args.fpp:5:1 + | +5 | / expand T(constant 10) { +6 | | +7 | | constant a = 0 +8 | | +9 | | } + | |_^ expected 0 template arguments, got 1 + | + ::: [ local path prefix ]/template/source_expand_bad_args.fpp:1:1 + | +1 | / module template T { +2 | | +3 | | } + | |_- template defined here + diff --git a/fpp_analysis/tests/template/source_expand_include.fpp b/fpp_analysis/tests/template/source_expand_include.fpp new file mode 100644 index 00000000..7f226a7d --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_include.fpp @@ -0,0 +1,12 @@ +# An include specifier in an expansion written in the source is resolved, and a +# diagnostic inside it reports where the file is included + +module template T { + +} + +expand T { + + include "source_expand_include.fppi" + +} diff --git a/fpp_analysis/tests/template/source_expand_include.fppi b/fpp_analysis/tests/template/source_expand_include.fppi new file mode 100644 index 00000000..c5287dd7 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_include.fppi @@ -0,0 +1 @@ +constant a = undefinedX diff --git a/fpp_analysis/tests/template/source_expand_include.ref.txt b/fpp_analysis/tests/template/source_expand_include.ref.txt new file mode 100644 index 00000000..ab541847 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_include.ref.txt @@ -0,0 +1,8 @@ + --> [ local path prefix ]/template/source_expand_include.fppi:1:14 + | +1 | constant a = undefinedX + | ^^^^^^^^^^ symbol undefinedX is not defined + | + = note: included from [ local path prefix ]/template/source_expand_include.fpp:10:11 + = note: looking for a constant here + diff --git a/fpp_analysis/tests/template/source_expand_member_use_ok.fpp b/fpp_analysis/tests/template/source_expand_member_use_ok.fpp new file mode 100644 index 00000000..cfbf3b03 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_member_use_ok.fpp @@ -0,0 +1,28 @@ +# The definitions of an expansion written in the source are definitions of the +# enclosing module, and the uses inside it resolve against them and against the +# template parameters + +module template T(constant p: U32) { + +} + +module M { + + expand T(constant 1) { + + constant a = p + + constant b = a + 1 + + state machine S { + initial enter S1 + state S1 + } + + } + +} + +constant c = M.a + M.b + +constant d = M.S.State.S1 diff --git a/fpp_analysis/tests/template/source_expand_member_use_ok.ref.txt b/fpp_analysis/tests/template/source_expand_member_use_ok.ref.txt new file mode 100644 index 00000000..e69de29b diff --git a/fpp_analysis/tests/template/source_expand_ok.fpp b/fpp_analysis/tests/template/source_expand_ok.fpp new file mode 100644 index 00000000..b0de9cc1 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_ok.fpp @@ -0,0 +1,9 @@ +module template T { + +} + +expand T { + + constant a = 0 + +} diff --git a/fpp_analysis/tests/template/source_expand_ok.ref.txt b/fpp_analysis/tests/template/source_expand_ok.ref.txt new file mode 100644 index 00000000..e69de29b diff --git a/fpp_analysis/tests/template/source_expand_undefined_symbol.fpp b/fpp_analysis/tests/template/source_expand_undefined_symbol.fpp new file mode 100644 index 00000000..76a128dd --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_undefined_symbol.fpp @@ -0,0 +1,9 @@ +module template T { + +} + +expand T { + + constant a = b + +} diff --git a/fpp_analysis/tests/template/source_expand_undefined_symbol.ref.txt b/fpp_analysis/tests/template/source_expand_undefined_symbol.ref.txt new file mode 100644 index 00000000..94c59831 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_undefined_symbol.ref.txt @@ -0,0 +1,7 @@ + --> [ local path prefix ]/template/source_expand_undefined_symbol.fpp:7:16 + | +7 | constant a = b + | ^ symbol b is not defined + | + = note: looking for a constant here + diff --git a/fpp_analysis/tests/template/source_expand_undefined_template.fpp b/fpp_analysis/tests/template/source_expand_undefined_template.fpp new file mode 100644 index 00000000..ee11b4d3 --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_undefined_template.fpp @@ -0,0 +1,7 @@ +# The template an expansion written in the source names is not defined + +expand T { + + constant a = 0 + +} diff --git a/fpp_analysis/tests/template/source_expand_undefined_template.ref.txt b/fpp_analysis/tests/template/source_expand_undefined_template.ref.txt new file mode 100644 index 00000000..b65f00fb --- /dev/null +++ b/fpp_analysis/tests/template/source_expand_undefined_template.ref.txt @@ -0,0 +1,7 @@ + --> [ local path prefix ]/template/source_expand_undefined_template.fpp:3:8 + | +3 | expand T { + | ^ symbol T is not defined + | + = note: looking for a template here + diff --git a/fpp_analysis/tests/template/template_def_in_expand.fpp b/fpp_analysis/tests/template/template_def_in_expand.fpp new file mode 100644 index 00000000..ecdf65e9 --- /dev/null +++ b/fpp_analysis/tests/template/template_def_in_expand.fpp @@ -0,0 +1,11 @@ +module template T1 { + +} + +expand T1 { + + module template T2 { + + } + +} diff --git a/fpp_analysis/tests/template/template_def_in_expand.ref.txt b/fpp_analysis/tests/template/template_def_in_expand.ref.txt new file mode 100644 index 00000000..9627a8bc --- /dev/null +++ b/fpp_analysis/tests/template/template_def_in_expand.ref.txt @@ -0,0 +1,16 @@ + --> [ local path prefix ]/template/template_def_in_expand.fpp:7:3 + | + 7 | / module template T2 { + 8 | | + 9 | | } + | |___^ module template definitions cannot appear inside a template expansion + | + ::: [ local path prefix ]/template/template_def_in_expand.fpp:5:1 + | + 5 | / expand T1 { + 6 | | + 7 | | module template T2 { +... | +11 | | } + | |_- enclosing template expansion is here + diff --git a/fpp_analysis/tests/template/test.rs b/fpp_analysis/tests/template/test.rs index 9b3ba90c..f2d29c91 100644 --- a/fpp_analysis/tests/template/test.rs +++ b/fpp_analysis/tests/template/test.rs @@ -74,11 +74,6 @@ fn chained_constants() { run_test("template/chained_constants") } -#[test] -fn composition_invalid() { - run_test("template/composition_invalid") -} - #[test] fn constant_param_array_promote() { run_test("template/constant_param_array_promote") @@ -109,6 +104,11 @@ fn duplicate_def() { run_test("template/duplicate_def") } +#[test] +fn expand_in_template_def() { + run_test("template/expand_in_template_def") +} + #[test] fn import_two_expansions_ok() { run_test_unconnected("template/import_two_expansions_ok") @@ -185,13 +185,13 @@ fn interfaces_valid_port_instance_name_aliased() { } #[test] -fn invalid_nested() { - run_test("template/invalid_nested") +fn module_merge_ok() { + run_test("template/module_merge_ok") } #[test] -fn module_merge_ok() { - run_test("template/module_merge_ok") +fn nested_expand() { + run_test("template/nested_expand") } #[test] @@ -204,6 +204,11 @@ fn nested_scope_resolution_ok() { run_test("template/nested_scope_resolution_ok") } +#[test] +fn nested_template_def() { + run_test("template/nested_template_def") +} + #[test] fn param_body_def_different_group_ok() { run_test("template/param_body_def_different_group_ok") @@ -234,6 +239,21 @@ fn parameter_shadow_ok() { run_test("template/parameter_shadow_ok") } +#[test] +fn source_expand_bad_args() { + run_test("template/source_expand_bad_args") +} + +#[test] +fn source_expand_ok() { + run_test("template/source_expand_ok") +} + +#[test] +fn source_expand_undefined_symbol() { + run_test("template/source_expand_undefined_symbol") +} + #[test] fn struct_parameter_alias_default_ok() { run_test("template/struct_parameter_alias_default_ok") @@ -284,6 +304,11 @@ fn struct_parameter_omitted_member_ok() { run_test("template/struct_parameter_omitted_member_ok") } +#[test] +fn template_def_in_expand() { + run_test("template/template_def_in_expand") +} + #[test] fn type_arg_string() { run_test("template/type_arg_string") @@ -393,13 +418,15 @@ fn expand_absolute_undefined() { run_test("template/expand_absolute_undefined") } -/// An expansion specifier in a template body is reported at the first expansion +/// An expansion specifier in a template body is reported in source order, not +/// at the first expansion #[test] fn expand_before_invalid_composition() { run_test("template/expand_before_invalid_composition") } -/// A nested template in a template body is reported at the first expansion +/// A nested template in a template body is reported in source order, not at +/// the first expansion #[test] fn expand_before_invalid_nested() { run_test("template/expand_before_invalid_nested") @@ -542,3 +569,21 @@ fn interface_arg_file_order() { "template/interface_arg_file_order_a", ) } + +/// An expansion written in the source that names an undefined template +#[test] +fn source_expand_undefined_template() { + run_test("template/source_expand_undefined_template") +} + +/// The definitions and uses of an expansion written in the source +#[test] +fn source_expand_member_use_ok() { + run_test("template/source_expand_member_use_ok") +} + +/// An include specifier in an expansion written in the source +#[test] +fn source_expand_include() { + run_test("template/source_expand_include") +} diff --git a/fpp_ast/src/template.rs b/fpp_ast/src/template.rs index db52c77c..13f1bf4d 100644 --- a/fpp_ast/src/template.rs +++ b/fpp_ast/src/template.rs @@ -99,7 +99,9 @@ pub struct DefModuleTemplate { pub struct SpecTemplateExpand { pub template: QualIdent, pub args: TemplateArgList, - /// The members generated by expanding the template, present after template - /// expansion has run + /// The result of expanding the template. A specifier with members is an + /// expanded template, and one without is an unexpanded template. The members + /// are either written in the source, in the form `expand T { ... }`, or + /// generated by template expansion. pub members: Option>, } diff --git a/fpp_format/tests/formatter.rs b/fpp_format/tests/formatter.rs index 7b016a90..a1545f5c 100644 --- a/fpp_format/tests/formatter.rs +++ b/fpp_format/tests/formatter.rs @@ -131,6 +131,7 @@ fmt_test!(port, "port"); fmt_test!(topology, "topology"); fmt_test!(state_machine, "state-machine"); fmt_test!(template, "template"); +fmt_test!(template_expanded, "template-expanded"); fmt_test!(state_machine_defs, "state-machine-defs"); fmt_test!(instances_locate, "instances-locate"); fmt_test!(interface_include, "interface-include"); diff --git a/fpp_format/tests/template-expanded.fpp b/fpp_format/tests/template-expanded.fpp new file mode 100644 index 00000000..fa5af0a9 --- /dev/null +++ b/fpp_format/tests/template-expanded.fpp @@ -0,0 +1,21 @@ +module template T(constant p: U32) { + constant c = p +} + +module M { + @ An expanded template + expand T(constant 1) { constant c = 1 + @ A nested module + module N { + constant d = c } + + } + + expand T(constant 2) {} + + expand T( + constant 3 + ) { + constant c = 3 + } +} diff --git a/fpp_format/tests/template-expanded.ref.fpp b/fpp_format/tests/template-expanded.ref.fpp new file mode 100644 index 00000000..96f13eea --- /dev/null +++ b/fpp_format/tests/template-expanded.ref.fpp @@ -0,0 +1,22 @@ +module template T(constant p: U32) { + constant c = p +} + +module M { + @ An expanded template + expand T(constant 1) { + constant c = 1 + @ A nested module + module N { + constant d = c + } + + } + + expand T(constant 2) { + } + + expand T(constant 3) { + constant c = 3 + } +} diff --git a/fpp_lsp_parser/src/grammar/module.rs b/fpp_lsp_parser/src/grammar/module.rs index fdea72dc..305e69fd 100644 --- a/fpp_lsp_parser/src/grammar/module.rs +++ b/fpp_lsp_parser/src/grammar/module.rs @@ -136,6 +136,17 @@ fn spec_template_expand(p: &mut Parser) { p.bump(EXPAND_KW); qual_ident(p); template_arg_list(p); + if p.at(LEFT_CURLY) { + member_list( + p, + LEFT_CURLY, + RIGHT_CURLY, + module_member, + SEMI, + MODULE_MEMBER_LIST, + "module member expected", + ) + } m.complete(p, SPEC_TEMPLATE_EXPAND); } diff --git a/fpp_lsp_parser/tests/include-module-template-expand.ref.txt b/fpp_lsp_parser/tests/include-module-template-expand.ref.txt new file mode 100644 index 00000000..38577787 --- /dev/null +++ b/fpp_lsp_parser/tests/include-module-template-expand.ref.txt @@ -0,0 +1,33 @@ +ROOT@0..67 + DEF_MODULE_TEMPLATE@0..22 + MODULE_KW@0..6 "module" + WHITESPACE@6..7 " " + TEMPLATE_KW@7..15 "template" + WHITESPACE@15..16 " " + NAME@16..17 + IDENT@16..17 "T" + WHITESPACE@17..18 " " + MODULE_MEMBER_LIST@18..22 + LEFT_CURLY@18..19 "{" + EOL@19..21 "\n\n" + RIGHT_CURLY@21..22 "}" + EOL@22..24 "\n\n" + SPEC_TEMPLATE_EXPAND@24..66 + EXPAND_KW@24..30 "expand" + WHITESPACE@30..31 " " + QUAL_IDENT@31..32 + IDENT@31..32 "T" + WHITESPACE@32..33 " " + MODULE_MEMBER_LIST@33..66 + LEFT_CURLY@33..34 "{" + EOL@34..35 "\n" + WHITESPACE@35..37 " " + EOL@37..38 "\n" + WHITESPACE@38..40 " " + SPEC_INCLUDE@40..63 + INCLUDE_KW@40..47 "include" + WHITESPACE@47..48 " " + LITERAL_STRING@48..63 "\"constant.fppi\"" + EOL@63..65 "\n\n" + RIGHT_CURLY@65..66 "}" + EOL@66..67 "\n" diff --git a/fpp_lsp_parser/tests/include-module-template.ref.txt b/fpp_lsp_parser/tests/include-module-template.ref.txt new file mode 100644 index 00000000..d0bcca88 --- /dev/null +++ b/fpp_lsp_parser/tests/include-module-template.ref.txt @@ -0,0 +1,20 @@ +ROOT@0..50 + DEF_MODULE_TEMPLATE@0..49 + MODULE_KW@0..6 "module" + WHITESPACE@6..7 " " + TEMPLATE_KW@7..15 "template" + WHITESPACE@15..16 " " + NAME@16..17 + IDENT@16..17 "T" + WHITESPACE@17..18 " " + MODULE_MEMBER_LIST@18..49 + LEFT_CURLY@18..19 "{" + EOL@19..21 "\n\n" + WHITESPACE@21..23 " " + SPEC_INCLUDE@23..46 + INCLUDE_KW@23..30 "include" + WHITESPACE@30..31 " " + LITERAL_STRING@31..46 "\"constant.fppi\"" + EOL@46..48 "\n\n" + RIGHT_CURLY@48..49 "}" + EOL@49..50 "\n" diff --git a/fpp_lsp_parser/tests/integration.rs b/fpp_lsp_parser/tests/integration.rs index 459a69b7..06f64dc5 100644 --- a/fpp_lsp_parser/tests/integration.rs +++ b/fpp_lsp_parser/tests/integration.rs @@ -149,6 +149,16 @@ fn include_module() { run_test_from_fpp_parser("include-module") } +#[test] +fn include_module_template() { + run_test_from_fpp_parser("include-module-template") +} + +#[test] +fn include_module_template_expand() { + run_test_from_fpp_parser("include-module-template-expand") +} + #[test] fn include_parse_error() { run_test_from_fpp_parser("include-parse-error") @@ -282,6 +292,6 @@ fn guarded_command_nested_array() { } #[test] -fn template() { - run_test_from_fpp_parser("template") +fn module_template() { + run_test_from_fpp_parser("module-template") } diff --git a/fpp_lsp_parser/tests/module-template.ref.txt b/fpp_lsp_parser/tests/module-template.ref.txt new file mode 100644 index 00000000..bd09409f --- /dev/null +++ b/fpp_lsp_parser/tests/module-template.ref.txt @@ -0,0 +1,218 @@ +ROOT@0..677 + DEF_MODULE_TEMPLATE@0..353 + MODULE_KW@0..6 "module" + WHITESPACE@6..7 " " + TEMPLATE_KW@7..15 "template" + WHITESPACE@15..16 " " + NAME@16..20 + IDENT@16..20 "Tmpl" + TEMPLATE_PARAM_LIST@20..271 + LEFT_PAREN@20..21 "(" + EOL@21..22 "\n" + WHITESPACE@22..26 " " + PRE_ANNOTATION@26..55 "@ Constant template p ..." + EOL@55..56 "\n" + WHITESPACE@56..60 " " + TEMPLATE_PARAM@60..80 + CONSTANT_KW@60..68 "constant" + WHITESPACE@68..69 " " + NAME@69..70 + IDENT@69..70 "c" + COLON@70..71 ":" + WHITESPACE@71..72 " " + TYPE_NAME@72..80 + QUAL_IDENT@72..80 + IDENT@72..80 "SomeType" + WHITESPACE@80..81 " " + POST_ANNOTATION@81..102 "@< Constant parameter" + EOL@102..104 "\n\n" + WHITESPACE@104..108 " " + PRE_ANNOTATION@108..133 "@ Type template param ..." + EOL@133..134 "\n" + WHITESPACE@134..138 " " + TEMPLATE_PARAM@138..152 + TYPE_KW@138..142 "type" + WHITESPACE@142..143 " " + NAME@143..152 + IDENT@143..152 "TypeParam" + WHITESPACE@152..153 " " + POST_ANNOTATION@153..170 "@< Type parameter" + EOL@170..172 "\n\n" + WHITESPACE@172..176 " " + PRE_ANNOTATION@176..215 "@ Interface instance ..." + EOL@215..216 "\n" + WHITESPACE@216..220 " " + TEMPLATE_PARAM@220..247 + INSTANCE_KW@220..228 "instance" + WHITESPACE@228..229 " " + NAME@229..233 + IDENT@229..233 "inst" + COLON@233..234 ":" + WHITESPACE@234..235 " " + QUAL_IDENT@235..247 + IDENT@235..247 "InterfaceDef" + WHITESPACE@247..248 " " + POST_ANNOTATION@248..269 "@< Instance parameter" + EOL@269..270 "\n" + RIGHT_PAREN@270..271 ")" + WHITESPACE@271..272 " " + MODULE_MEMBER_LIST@272..353 + LEFT_CURLY@272..273 "{" + EOL@273..274 "\n" + WHITESPACE@274..278 " " + DEF_ARRAY@278..303 + ARRAY_KW@278..283 "array" + WHITESPACE@283..284 " " + NAME@284..287 + IDENT@284..287 "Arr" + WHITESPACE@287..288 " " + EQUALS@288..289 "=" + WHITESPACE@289..290 " " + INDEX_OR_SIZE@290..293 + LEFT_SQUARE@290..291 "[" + EXPR@291..292 + EXPR_LITERAL@291..292 + LITERAL_INT@291..292 "3" + RIGHT_SQUARE@292..293 "]" + WHITESPACE@293..294 " " + TYPE_NAME@294..303 + QUAL_IDENT@294..303 + IDENT@294..303 "TypeParam" + EOL@303..305 "\n\n" + WHITESPACE@305..309 " " + DEF_TOPOLOGY@309..351 + TOPOLOGY_KW@309..317 "topology" + WHITESPACE@317..318 " " + NAME@318..321 + IDENT@318..321 "Top" + WHITESPACE@321..322 " " + TOPOLOGY_MEMBER_LIST@322..351 + LEFT_CURLY@322..323 "{" + EOL@323..324 "\n" + WHITESPACE@324..332 " " + SPEC_INSTANCE@332..345 + INSTANCE_KW@332..340 "instance" + WHITESPACE@340..341 " " + QUAL_IDENT@341..345 + IDENT@341..345 "inst" + EOL@345..346 "\n" + WHITESPACE@346..350 " " + RIGHT_CURLY@350..351 "}" + EOL@351..352 "\n" + RIGHT_CURLY@352..353 "}" + EOL@353..355 "\n\n" + DEF_MODULE@355..508 + MODULE_KW@355..361 "module" + WHITESPACE@361..362 " " + NAME@362..372 + IDENT@362..372 "Unexpanded" + WHITESPACE@372..373 " " + MODULE_MEMBER_LIST@373..508 + LEFT_CURLY@373..374 "{" + EOL@374..376 "\n\n" + WHITESPACE@376..378 " " + PRE_ANNOTATION@378..410 "@ Unexpanded expansio ..." + EOL@410..411 "\n" + WHITESPACE@411..413 " " + SPEC_TEMPLATE_EXPAND@413..469 + EXPAND_KW@413..419 "expand" + WHITESPACE@419..420 " " + QUAL_IDENT@420..424 + IDENT@420..424 "Tmpl" + TEMPLATE_ARG_LIST@424..469 + LEFT_PAREN@424..425 "(" + TEMPLATE_ARG@425..436 + CONSTANT_KW@425..433 "constant" + WHITESPACE@433..434 " " + EXPR@434..436 + EXPR_LITERAL@434..436 + LITERAL_INT@434..436 "10" + COMMA@436..437 "," + WHITESPACE@437..438 " " + TEMPLATE_ARG@438..447 + TYPE_KW@438..442 "type" + WHITESPACE@442..443 " " + TYPE_NAME@443..447 + QUAL_IDENT@443..447 + IDENT@443..447 "Type" + COMMA@447..448 "," + WHITESPACE@448..449 " " + TEMPLATE_ARG@449..468 + INSTANCE_KW@449..457 "instance" + WHITESPACE@457..458 " " + QUAL_IDENT@458..468 + DOT@458..459 "." + IDENT@459..468 "instance1" + RIGHT_PAREN@468..469 ")" + EOL@469..470 "\n" + WHITESPACE@470..472 " " + POST_ANNOTATION@472..505 "@< Unexpanded expansi ..." + EOL@505..507 "\n\n" + RIGHT_CURLY@507..508 "}" + EOL@508..510 "\n\n" + DEF_MODULE@510..676 + MODULE_KW@510..516 "module" + WHITESPACE@516..517 " " + NAME@517..525 + IDENT@517..525 "Expanded" + WHITESPACE@525..526 " " + MODULE_MEMBER_LIST@526..676 + LEFT_CURLY@526..527 "{" + EOL@527..529 "\n\n" + WHITESPACE@529..531 " " + PRE_ANNOTATION@531..561 "@ Expanded expansion ..." + EOL@561..562 "\n" + WHITESPACE@562..564 " " + SPEC_TEMPLATE_EXPAND@564..639 + EXPAND_KW@564..570 "expand" + WHITESPACE@570..571 " " + QUAL_IDENT@571..575 + IDENT@571..575 "Tmpl" + TEMPLATE_ARG_LIST@575..620 + LEFT_PAREN@575..576 "(" + TEMPLATE_ARG@576..587 + CONSTANT_KW@576..584 "constant" + WHITESPACE@584..585 " " + EXPR@585..587 + EXPR_LITERAL@585..587 + LITERAL_INT@585..587 "10" + COMMA@587..588 "," + WHITESPACE@588..589 " " + TEMPLATE_ARG@589..598 + TYPE_KW@589..593 "type" + WHITESPACE@593..594 " " + TYPE_NAME@594..598 + QUAL_IDENT@594..598 + IDENT@594..598 "Type" + COMMA@598..599 "," + WHITESPACE@599..600 " " + TEMPLATE_ARG@600..619 + INSTANCE_KW@600..608 "instance" + WHITESPACE@608..609 " " + QUAL_IDENT@609..619 + DOT@609..610 "." + IDENT@610..619 "instance1" + RIGHT_PAREN@619..620 ")" + WHITESPACE@620..621 " " + MODULE_MEMBER_LIST@621..639 + LEFT_CURLY@621..622 "{" + WHITESPACE@622..623 " " + DEF_CONSTANT@623..637 + CONSTANT_KW@623..631 "constant" + WHITESPACE@631..632 " " + NAME@632..633 + IDENT@632..633 "a" + WHITESPACE@633..634 " " + EQUALS@634..635 "=" + WHITESPACE@635..636 " " + EXPR@636..637 + EXPR_LITERAL@636..637 + LITERAL_INT@636..637 "0" + WHITESPACE@637..638 " " + RIGHT_CURLY@638..639 "}" + EOL@639..640 "\n" + WHITESPACE@640..642 " " + POST_ANNOTATION@642..673 "@< Expanded expansion ..." + EOL@673..675 "\n\n" + RIGHT_CURLY@675..676 "}" + EOL@676..677 "\n" diff --git a/fpp_lsp_parser/tests/parser_spec.rs b/fpp_lsp_parser/tests/parser_spec.rs index a7c642c6..e7edb450 100644 --- a/fpp_lsp_parser/tests/parser_spec.rs +++ b/fpp_lsp_parser/tests/parser_spec.rs @@ -481,33 +481,73 @@ const CASES: &[(&str, &str, bool)] = &[ true, ), ("spec template expand OK", "expand T", true), + ( + "spec template expand OK", + "expand T { constant a = 0 }", + true, + ), ("spec template expand OK", "expand M.T()", true), + ( + "spec template expand OK", + "expand M.T() { constant a = 0 }", + true, + ), ("spec template expand OK", "expand M.T(constant 1)", true), + ( + "spec template expand OK", + "expand M.T(constant 1) { constant a = 0 }", + true, + ), ( "spec template expand OK", "expand M.T(constant 1, constant 2)", true, ), + ( + "spec template expand OK", + "expand M.T(constant 1, constant 2) { constant a = 0 }", + true, + ), ( "spec template expand OK", "expand M.T(constant 1, constant 2, instance mod.$instance)", true, ), + ( + "spec template expand OK", + "expand M.T(constant 1, constant 2, instance mod.$instance) { constant a = 0 }", + true, + ), ( "spec template expand OK", "expand M.T(constant 1, constant 2, instance .mod.$instance, type Type.Name)", true, ), + ( + "spec template expand OK", + "expand M.T(constant 1, constant 2, instance .mod.$instance, type Type.Name) { constant a = 0 }", + true, + ), ( "spec template expand OK", "expand M.T(constant 1, instance .inst1, type U32)", true, ), + ( + "spec template expand OK", + "expand M.T(constant 1, instance .inst1, type U32) { constant a = 0 }", + true, + ), ( "spec template expand OK", "expand M.T(constant 1, constant 2, constant {member=1}, constant [1, 2])", true, ), + ( + "spec template expand OK", + "expand M.T(constant 1, constant 2, constant {member=1}, constant [1, 2]) { constant a = 0 }", + true, + ), ("spec include OK", "include \"file.fpp\"", true), ( "spec internal port OK", diff --git a/fpp_lsp_parser/tests/template.ref.txt b/fpp_lsp_parser/tests/template.ref.txt deleted file mode 100644 index 9af14d4b..00000000 --- a/fpp_lsp_parser/tests/template.ref.txt +++ /dev/null @@ -1,138 +0,0 @@ -ROOT@0..457 - DEF_MODULE_TEMPLATE@0..353 - MODULE_KW@0..6 "module" - WHITESPACE@6..7 " " - TEMPLATE_KW@7..15 "template" - WHITESPACE@15..16 " " - NAME@16..20 - IDENT@16..20 "Tmpl" - TEMPLATE_PARAM_LIST@20..271 - LEFT_PAREN@20..21 "(" - EOL@21..22 "\n" - WHITESPACE@22..26 " " - PRE_ANNOTATION@26..55 "@ Constant template p ..." - EOL@55..56 "\n" - WHITESPACE@56..60 " " - TEMPLATE_PARAM@60..80 - CONSTANT_KW@60..68 "constant" - WHITESPACE@68..69 " " - NAME@69..70 - IDENT@69..70 "c" - COLON@70..71 ":" - WHITESPACE@71..72 " " - TYPE_NAME@72..80 - QUAL_IDENT@72..80 - IDENT@72..80 "SomeType" - WHITESPACE@80..81 " " - POST_ANNOTATION@81..102 "@< Constant parameter" - EOL@102..104 "\n\n" - WHITESPACE@104..108 " " - PRE_ANNOTATION@108..133 "@ Type template param ..." - EOL@133..134 "\n" - WHITESPACE@134..138 " " - TEMPLATE_PARAM@138..152 - TYPE_KW@138..142 "type" - WHITESPACE@142..143 " " - NAME@143..152 - IDENT@143..152 "TypeParam" - WHITESPACE@152..153 " " - POST_ANNOTATION@153..170 "@< Type parameter" - EOL@170..172 "\n\n" - WHITESPACE@172..176 " " - PRE_ANNOTATION@176..215 "@ Interface instance ..." - EOL@215..216 "\n" - WHITESPACE@216..220 " " - TEMPLATE_PARAM@220..247 - INSTANCE_KW@220..228 "instance" - WHITESPACE@228..229 " " - NAME@229..233 - IDENT@229..233 "inst" - COLON@233..234 ":" - WHITESPACE@234..235 " " - QUAL_IDENT@235..247 - IDENT@235..247 "InterfaceDef" - WHITESPACE@247..248 " " - POST_ANNOTATION@248..269 "@< Instance parameter" - EOL@269..270 "\n" - RIGHT_PAREN@270..271 ")" - WHITESPACE@271..272 " " - MODULE_MEMBER_LIST@272..353 - LEFT_CURLY@272..273 "{" - EOL@273..274 "\n" - WHITESPACE@274..278 " " - DEF_ARRAY@278..303 - ARRAY_KW@278..283 "array" - WHITESPACE@283..284 " " - NAME@284..287 - IDENT@284..287 "Arr" - WHITESPACE@287..288 " " - EQUALS@288..289 "=" - WHITESPACE@289..290 " " - INDEX_OR_SIZE@290..293 - LEFT_SQUARE@290..291 "[" - EXPR@291..292 - EXPR_LITERAL@291..292 - LITERAL_INT@291..292 "3" - RIGHT_SQUARE@292..293 "]" - WHITESPACE@293..294 " " - TYPE_NAME@294..303 - QUAL_IDENT@294..303 - IDENT@294..303 "TypeParam" - EOL@303..305 "\n\n" - WHITESPACE@305..309 " " - DEF_TOPOLOGY@309..351 - TOPOLOGY_KW@309..317 "topology" - WHITESPACE@317..318 " " - NAME@318..321 - IDENT@318..321 "Top" - WHITESPACE@321..322 " " - TOPOLOGY_MEMBER_LIST@322..351 - LEFT_CURLY@322..323 "{" - EOL@323..324 "\n" - WHITESPACE@324..332 " " - SPEC_INSTANCE@332..345 - INSTANCE_KW@332..340 "instance" - WHITESPACE@340..341 " " - QUAL_IDENT@341..345 - IDENT@341..345 "inst" - EOL@345..346 "\n" - WHITESPACE@346..350 " " - RIGHT_CURLY@350..351 "}" - EOL@351..352 "\n" - RIGHT_CURLY@352..353 "}" - EOL@353..355 "\n\n" - PRE_ANNOTATION@355..376 "@ Expansion specifier" - EOL@376..377 "\n" - SPEC_TEMPLATE_EXPAND@377..433 - EXPAND_KW@377..383 "expand" - WHITESPACE@383..384 " " - QUAL_IDENT@384..388 - IDENT@384..388 "Tmpl" - TEMPLATE_ARG_LIST@388..433 - LEFT_PAREN@388..389 "(" - TEMPLATE_ARG@389..400 - CONSTANT_KW@389..397 "constant" - WHITESPACE@397..398 " " - EXPR@398..400 - EXPR_LITERAL@398..400 - LITERAL_INT@398..400 "10" - COMMA@400..401 "," - WHITESPACE@401..402 " " - TEMPLATE_ARG@402..411 - TYPE_KW@402..406 "type" - WHITESPACE@406..407 " " - TYPE_NAME@407..411 - QUAL_IDENT@407..411 - IDENT@407..411 "Type" - COMMA@411..412 "," - WHITESPACE@412..413 " " - TEMPLATE_ARG@413..432 - INSTANCE_KW@413..421 "instance" - WHITESPACE@421..422 " " - QUAL_IDENT@422..432 - DOT@422..423 "." - IDENT@423..432 "instance1" - RIGHT_PAREN@432..433 ")" - EOL@433..434 "\n" - POST_ANNOTATION@434..456 "@< Expansion specifier" - EOL@456..457 "\n" diff --git a/fpp_lsp_server/src/handlers.rs b/fpp_lsp_server/src/handlers.rs index f9ea9e29..498c960b 100644 --- a/fpp_lsp_server/src/handlers.rs +++ b/fpp_lsp_server/src/handlers.rs @@ -404,6 +404,7 @@ impl<'a> fpp_lsp_parser::Visitor for DocumentLinksVisitor<'a> { SyntaxKind::ROOT => VisitorResult::Recurse, SyntaxKind::DEF_MODULE => VisitorResult::Recurse, SyntaxKind::DEF_MODULE_TEMPLATE => VisitorResult::Recurse, + SyntaxKind::SPEC_TEMPLATE_EXPAND => VisitorResult::Recurse, SyntaxKind::MODULE_MEMBER_LIST => VisitorResult::Recurse, SyntaxKind::DEF_COMPONENT => VisitorResult::Recurse, SyntaxKind::COMPONENT_MEMBER_LIST => VisitorResult::Recurse, @@ -1222,9 +1223,17 @@ pub fn handle_completion( // which is a *sibling* of the rule rather than a // descendant (nothing after the keyword parsed, // so the rule ends at it). Fall back to the rule - // around the last real token to the left. - let mut ancestors = - element.ancestors().chain(left_token.parent_ancestors()); + // around the last real token to the left. The + // search stops at the module member list around + // the cursor: a rule outside it, such as the + // template expansion specifier whose members + // the list holds, does not decide the name. + let in_member = + |n: &SyntaxNode| n.kind() != SyntaxKind::MODULE_MEMBER_LIST; + let mut ancestors = element + .ancestors() + .take_while(in_member) + .chain(left_token.parent_ancestors().take_while(in_member)); if let Some(ng) = ancestors.find_map(|n| match n.kind() { SyntaxKind::DEF_COMPONENT_INSTANCE => { @@ -1824,4 +1833,43 @@ module M { let _ = std::fs::remove_dir_all(&dir); } + + const WRITTEN_EXPANSION_SRC: &str = "\ +module M { + module template Tmpl(constant Size: U32) { + } + expand Tmpl(constant 3) { + constant written = Size + constant other = written + } + constant outside = written +} +"; + + /// The members of an expansion written in the source are analyzed where they + /// are written, so a use inside them hovers as the parameter or definition it + /// names, and a use outside them reaches their definitions. + #[test] + fn hover_on_use_inside_written_expansion() { + let dir = temp_dir("template-written-expand"); + let (state, uri) = state_for(&dir, WRITTEN_EXPANSION_SRC); + + for (needle, nth, expected) in [ + ("Size", 1, "(Constant Template Parameter) Size"), + ("written", 1, "M.written"), + ("written", 2, "M.written"), + ] { + let value = hover_at( + &state, + &uri, + position_of_nth(WRITTEN_EXPANSION_SRC, needle, nth), + ); + assert!( + value.contains(expected), + "hover on `{needle}` #{nth} should contain `{expected}`, got:\n{value}" + ); + } + + let _ = std::fs::remove_dir_all(&dir); + } } diff --git a/fpp_parser/src/add_state_enums.rs b/fpp_parser/src/add_state_enums.rs index d8220477..3339f2a9 100644 --- a/fpp_parser/src/add_state_enums.rs +++ b/fpp_parser/src/add_state_enums.rs @@ -32,6 +32,11 @@ fn module_member(member: &mut ModuleMember) { module_member(m); } } + ModuleMember::SpecTemplateExpand(spec) => { + for m in spec.members.iter_mut().flatten() { + module_member(m); + } + } ModuleMember::DefComponent(def) => { for m in &mut def.members { component_member(m); diff --git a/fpp_parser/src/parser.rs b/fpp_parser/src/parser.rs index 6a49ce4e..68a235de 100644 --- a/fpp_parser/src/parser.rs +++ b/fpp_parser/src/parser.rs @@ -3143,12 +3143,16 @@ impl<'a> Parser<'a> { self.commit(); let template = self.qual_ident()?; let args = self.template_arg_list()?; + let members = match self.peek(0) { + LeftCurly => Some(self.enclosed(LeftCurly, RightCurly, Parser::module_members)?), + _ => None, + }; Ok(SpecTemplateExpand { node_id: self.node(first.span()), template, args, - members: None, + members, }) } diff --git a/fpp_parser/src/parser/parser_spec.rs b/fpp_parser/src/parser/parser_spec.rs index bc681965..553f088b 100644 --- a/fpp_parser/src/parser/parser_spec.rs +++ b/fpp_parser/src/parser/parser_spec.rs @@ -605,13 +605,21 @@ fn spec_template_expand_ok() { |p| p.spec_template_expand(), &[ "expand T", + "expand T { constant a = 0 }", "expand M.T()", + "expand M.T() { constant a = 0 }", "expand M.T(constant 1)", + "expand M.T(constant 1) { constant a = 0 }", "expand M.T(constant 1, constant 2)", + "expand M.T(constant 1, constant 2) { constant a = 0 }", "expand M.T(constant 1, constant 2, instance mod.$instance)", + "expand M.T(constant 1, constant 2, instance mod.$instance) { constant a = 0 }", "expand M.T(constant 1, constant 2, instance .mod.$instance, type Type.Name)", + "expand M.T(constant 1, constant 2, instance .mod.$instance, type Type.Name) { constant a = 0 }", "expand M.T(constant 1, instance .inst1, type U32)", + "expand M.T(constant 1, instance .inst1, type U32) { constant a = 0 }", "expand M.T(constant 1, constant 2, constant {member=1}, constant [1, 2])", + "expand M.T(constant 1, constant 2, constant {member=1}, constant [1, 2]) { constant a = 0 }", ], ); } diff --git a/fpp_parser/src/resolve_spec_include.rs b/fpp_parser/src/resolve_spec_include.rs index f361facd..efc0fcb0 100644 --- a/fpp_parser/src/resolve_spec_include.rs +++ b/fpp_parser/src/resolve_spec_include.rs @@ -364,6 +364,22 @@ impl MutVisitor for ResolveIncludes { ControlFlow::Continue(()) } + /// An unexpanded template has no members to resolve + fn visit_spec_template_expand( + &self, + a: &mut Self::State, + node: &mut SpecTemplateExpand, + ) -> ControlFlow { + if let Some(members) = &mut node.members { + let old_members = std::mem::take(members); + for member in old_members.into_iter() { + self.module_member(a, member, members) + } + } + + ControlFlow::Continue(()) + } + fn visit_def_state( &self, a: &mut Self::State, diff --git a/fpp_parser/tests/include-module-template-expand.fpp b/fpp_parser/tests/include-module-template-expand.fpp new file mode 100644 index 00000000..8e05a24c --- /dev/null +++ b/fpp_parser/tests/include-module-template-expand.fpp @@ -0,0 +1,9 @@ +module template T { + +} + +expand T { + + include "constant.fppi" + +} diff --git a/fpp_parser/tests/include-module-template-expand.ref.txt b/fpp_parser/tests/include-module-template-expand.ref.txt new file mode 100644 index 00000000..3cf8d7dd --- /dev/null +++ b/fpp_parser/tests/include-module-template-expand.ref.txt @@ -0,0 +1,73 @@ +TransUnit( + [ + DefModuleTemplate { + name: Name { + data: "T", + node_id: Span { + start: [ local path prefix ]/include-module-template-expand.fpp:1:17, + end: [ local path prefix ]/include-module-template-expand.fpp:1:18, + }, + }, + params: [], + members: [], + node_id: Span { + start: [ local path prefix ]/include-module-template-expand.fpp:1:1, + end: [ local path prefix ]/include-module-template-expand.fpp:3:2, + }, + }, + SpecTemplateExpand { + template: Unqualified { + name: Ident { + data: "T", + node_id: Span { + start: [ local path prefix ]/include-module-template-expand.fpp:5:8, + end: [ local path prefix ]/include-module-template-expand.fpp:5:9, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/include-module-template-expand.fpp:5:8, + end: [ local path prefix ]/include-module-template-expand.fpp:5:9, + }, + }, + args: [], + members: Some( + [ + DefConstant { + name: Name { + data: "x", + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:10, + end: [ local path prefix ]/constant.fppi:2:11, + }, + }, + value: Expr { + kind: LiteralInt( + "0", + ), + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:14, + end: [ local path prefix ]/constant.fppi:2:15, + }, + }, + is_dictionary_def: false, + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:1, + end: [ local path prefix ]/constant.fppi:2:15, + }, + pre_annotation: [ + "Included constant", + ], + post_annotation: [ + "Included constant", + ], + }, + ], + ), + node_id: Span { + start: [ local path prefix ]/include-module-template-expand.fpp:5:1, + end: [ local path prefix ]/include-module-template-expand.fpp:9:2, + }, + }, + ], +) \ No newline at end of file diff --git a/fpp_parser/tests/include-module-template.fpp b/fpp_parser/tests/include-module-template.fpp new file mode 100644 index 00000000..b1d2ecf3 --- /dev/null +++ b/fpp_parser/tests/include-module-template.fpp @@ -0,0 +1,5 @@ +module template T { + + include "constant.fppi" + +} diff --git a/fpp_parser/tests/include-module-template.ref.txt b/fpp_parser/tests/include-module-template.ref.txt new file mode 100644 index 00000000..e29b624a --- /dev/null +++ b/fpp_parser/tests/include-module-template.ref.txt @@ -0,0 +1,49 @@ +TransUnit( + [ + DefModuleTemplate { + name: Name { + data: "T", + node_id: Span { + start: [ local path prefix ]/include-module-template.fpp:1:17, + end: [ local path prefix ]/include-module-template.fpp:1:18, + }, + }, + params: [], + members: [ + DefConstant { + name: Name { + data: "x", + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:10, + end: [ local path prefix ]/constant.fppi:2:11, + }, + }, + value: Expr { + kind: LiteralInt( + "0", + ), + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:14, + end: [ local path prefix ]/constant.fppi:2:15, + }, + }, + is_dictionary_def: false, + node_id: Span { + start: [ local path prefix ]/constant.fppi:2:1, + end: [ local path prefix ]/constant.fppi:2:15, + }, + pre_annotation: [ + "Included constant", + ], + post_annotation: [ + "Included constant", + ], + }, + ], + node_id: Span { + start: [ local path prefix ]/include-module-template.fpp:1:1, + end: [ local path prefix ]/include-module-template.fpp:5:2, + }, + }, + ], +) \ No newline at end of file diff --git a/fpp_parser/tests/integration.rs b/fpp_parser/tests/integration.rs index f471e7f5..7e2b01bd 100644 --- a/fpp_parser/tests/integration.rs +++ b/fpp_parser/tests/integration.rs @@ -86,6 +86,11 @@ fn comments() { run_test("comments") } +#[test] +fn dictionary_specifier() { + run_test("dictionary-specifier") +} + #[test] fn embedded_tab() { run_test("embedded-tab") @@ -136,6 +141,16 @@ fn include_module() { run_test("include-module") } +#[test] +fn include_module_template() { + run_test("include-module-template") +} + +#[test] +fn include_module_template_expand() { + run_test("include-module-template-expand") +} + #[test] fn include_parent_dir() { run_test("subdir/include-parent-dir") @@ -161,6 +176,16 @@ fn include_topology() { run_test("include-topology") } +#[test] +fn module_template() { + run_test("module-template") +} + +#[test] +fn numeric_shift() { + run_test("numeric-shift") +} + #[test] fn parse_error() { run_test("parse-error") @@ -186,11 +211,6 @@ fn syntax_kwd_names() { run_test("syntax-kwd-names") } -#[test] -fn template() { - run_test("template") -} - #[test] fn topology_ports() { run_test("topology-ports") @@ -201,16 +221,6 @@ fn two_input_files() { run_test_files(&["include-module", "include-component"], "two-input-files") } -#[test] -fn dictionary_specifier() { - run_test("dictionary-specifier") -} - -#[test] -fn numeric_shift() { - run_test("numeric-shift") -} - // Rust-only: the cycle entered at its third file #[test] fn include_cycle_3() { diff --git a/fpp_parser/tests/template.fpp b/fpp_parser/tests/module-template.fpp similarity index 52% rename from fpp_parser/tests/template.fpp rename to fpp_parser/tests/module-template.fpp index 74a9c4b6..0748e77c 100644 --- a/fpp_parser/tests/template.fpp +++ b/fpp_parser/tests/module-template.fpp @@ -15,6 +15,18 @@ module template Tmpl( } } -@ Expansion specifier -expand Tmpl(constant 10, type Type, instance .instance1) -@< Expansion specifier +module Unexpanded { + + @ Unexpanded expansion specifier + expand Tmpl(constant 10, type Type, instance .instance1) + @< Unexpanded expansion specifier + +} + +module Expanded { + + @ Expanded expansion specifier + expand Tmpl(constant 10, type Type, instance .instance1) { constant a = 0 } + @< Expanded expansion specifier + +} diff --git a/fpp_parser/tests/module-template.ref.txt b/fpp_parser/tests/module-template.ref.txt new file mode 100644 index 00000000..74ecd6e1 --- /dev/null +++ b/fpp_parser/tests/module-template.ref.txt @@ -0,0 +1,450 @@ +TransUnit( + [ + DefModuleTemplate { + name: Name { + data: "Tmpl", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:1:17, + end: [ local path prefix ]/module-template.fpp:1:21, + }, + }, + params: [ + TemplateParamConstant { + name: Name { + data: "c", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:3:14, + end: [ local path prefix ]/module-template.fpp:3:15, + }, + }, + type_name: TypeName { + kind: QualIdent( + Unqualified { + name: Ident { + data: "SomeType", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:3:17, + end: [ local path prefix ]/module-template.fpp:3:25, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:3:17, + end: [ local path prefix ]/module-template.fpp:3:25, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:3:17, + end: [ local path prefix ]/module-template.fpp:3:25, + }, + }, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:3:5, + end: [ local path prefix ]/module-template.fpp:3:25, + }, + pre_annotation: [ + "Constant template parameter", + ], + post_annotation: [ + "Constant parameter", + ], + }, + TemplateParamType { + name: Name { + data: "TypeParam", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:6:10, + end: [ local path prefix ]/module-template.fpp:6:19, + }, + }, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:6:5, + end: [ local path prefix ]/module-template.fpp:6:19, + }, + pre_annotation: [ + "Type template parameter", + ], + post_annotation: [ + "Type parameter", + ], + }, + TemplateParamInterface { + name: Name { + data: "inst", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:9:14, + end: [ local path prefix ]/module-template.fpp:9:18, + }, + }, + interface: Unqualified { + name: Ident { + data: "InterfaceDef", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:9:20, + end: [ local path prefix ]/module-template.fpp:9:32, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:9:20, + end: [ local path prefix ]/module-template.fpp:9:32, + }, + }, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:9:5, + end: [ local path prefix ]/module-template.fpp:9:32, + }, + pre_annotation: [ + "Interface instance template parameter", + ], + post_annotation: [ + "Instance parameter", + ], + }, + ], + members: [ + DefArray { + name: Name { + data: "Arr", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:11, + end: [ local path prefix ]/module-template.fpp:11:14, + }, + }, + size: Expr { + kind: LiteralInt( + "3", + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:18, + end: [ local path prefix ]/module-template.fpp:11:19, + }, + }, + elt_type: TypeName { + kind: QualIdent( + Unqualified { + name: Ident { + data: "TypeParam", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:21, + end: [ local path prefix ]/module-template.fpp:11:30, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:21, + end: [ local path prefix ]/module-template.fpp:11:30, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:21, + end: [ local path prefix ]/module-template.fpp:11:30, + }, + }, + default: None, + format: None, + is_dictionary_def: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:11:5, + end: [ local path prefix ]/module-template.fpp:11:30, + }, + }, + DefTopology { + is_deployment: false, + name: Name { + data: "Top", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:13:14, + end: [ local path prefix ]/module-template.fpp:13:17, + }, + }, + members: [ + SpecInstance { + instance: Unqualified { + name: Ident { + data: "inst", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:14:18, + end: [ local path prefix ]/module-template.fpp:14:22, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:14:18, + end: [ local path prefix ]/module-template.fpp:14:22, + }, + }, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:14:9, + end: [ local path prefix ]/module-template.fpp:14:22, + }, + }, + ], + implements: [], + node_id: Span { + start: [ local path prefix ]/module-template.fpp:13:5, + end: [ local path prefix ]/module-template.fpp:15:6, + }, + }, + ], + node_id: Span { + start: [ local path prefix ]/module-template.fpp:1:1, + end: [ local path prefix ]/module-template.fpp:16:2, + }, + }, + DefModule { + name: Name { + data: "Unexpanded", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:18:8, + end: [ local path prefix ]/module-template.fpp:18:18, + }, + }, + members: [ + SpecTemplateExpand { + template: Unqualified { + name: Ident { + data: "Tmpl", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:10, + end: [ local path prefix ]/module-template.fpp:21:14, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:10, + end: [ local path prefix ]/module-template.fpp:21:14, + }, + }, + args: [ + TemplateArg { + kind: Constant( + Expr { + kind: LiteralInt( + "10", + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:24, + end: [ local path prefix ]/module-template.fpp:21:26, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:15, + end: [ local path prefix ]/module-template.fpp:21:26, + }, + }, + TemplateArg { + kind: Type( + TypeName { + kind: QualIdent( + Unqualified { + name: Ident { + data: "Type", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:33, + end: [ local path prefix ]/module-template.fpp:21:37, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:33, + end: [ local path prefix ]/module-template.fpp:21:37, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:33, + end: [ local path prefix ]/module-template.fpp:21:37, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:28, + end: [ local path prefix ]/module-template.fpp:21:37, + }, + }, + TemplateArg { + kind: Interface( + Unqualified { + name: Ident { + data: "instance1", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:49, + end: [ local path prefix ]/module-template.fpp:21:58, + }, + }, + is_absolute: true, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:48, + end: [ local path prefix ]/module-template.fpp:21:58, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:39, + end: [ local path prefix ]/module-template.fpp:21:58, + }, + }, + ], + members: None, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:21:3, + end: [ local path prefix ]/module-template.fpp:21:59, + }, + pre_annotation: [ + "Unexpanded expansion specifier", + ], + post_annotation: [ + "Unexpanded expansion specifier", + ], + }, + ], + node_id: Span { + start: [ local path prefix ]/module-template.fpp:18:1, + end: [ local path prefix ]/module-template.fpp:24:2, + }, + }, + DefModule { + name: Name { + data: "Expanded", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:26:8, + end: [ local path prefix ]/module-template.fpp:26:16, + }, + }, + members: [ + SpecTemplateExpand { + template: Unqualified { + name: Ident { + data: "Tmpl", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:10, + end: [ local path prefix ]/module-template.fpp:29:14, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:10, + end: [ local path prefix ]/module-template.fpp:29:14, + }, + }, + args: [ + TemplateArg { + kind: Constant( + Expr { + kind: LiteralInt( + "10", + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:24, + end: [ local path prefix ]/module-template.fpp:29:26, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:15, + end: [ local path prefix ]/module-template.fpp:29:26, + }, + }, + TemplateArg { + kind: Type( + TypeName { + kind: QualIdent( + Unqualified { + name: Ident { + data: "Type", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:33, + end: [ local path prefix ]/module-template.fpp:29:37, + }, + }, + is_absolute: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:33, + end: [ local path prefix ]/module-template.fpp:29:37, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:33, + end: [ local path prefix ]/module-template.fpp:29:37, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:28, + end: [ local path prefix ]/module-template.fpp:29:37, + }, + }, + TemplateArg { + kind: Interface( + Unqualified { + name: Ident { + data: "instance1", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:49, + end: [ local path prefix ]/module-template.fpp:29:58, + }, + }, + is_absolute: true, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:48, + end: [ local path prefix ]/module-template.fpp:29:58, + }, + }, + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:39, + end: [ local path prefix ]/module-template.fpp:29:58, + }, + }, + ], + members: Some( + [ + DefConstant { + name: Name { + data: "a", + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:71, + end: [ local path prefix ]/module-template.fpp:29:72, + }, + }, + value: Expr { + kind: LiteralInt( + "0", + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:75, + end: [ local path prefix ]/module-template.fpp:29:76, + }, + }, + is_dictionary_def: false, + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:62, + end: [ local path prefix ]/module-template.fpp:29:76, + }, + }, + ], + ), + node_id: Span { + start: [ local path prefix ]/module-template.fpp:29:3, + end: [ local path prefix ]/module-template.fpp:29:78, + }, + pre_annotation: [ + "Expanded expansion specifier", + ], + post_annotation: [ + "Expanded expansion specifier", + ], + }, + ], + node_id: Span { + start: [ local path prefix ]/module-template.fpp:26:1, + end: [ local path prefix ]/module-template.fpp:32:2, + }, + }, + ], +) \ No newline at end of file diff --git a/fpp_parser/tests/template.ref.txt b/fpp_parser/tests/template.ref.txt deleted file mode 100644 index fe4a5965..00000000 --- a/fpp_parser/tests/template.ref.txt +++ /dev/null @@ -1,296 +0,0 @@ -TransUnit( - [ - DefModuleTemplate { - name: Name { - data: "Tmpl", - node_id: Span { - start: [ local path prefix ]/template.fpp:1:17, - end: [ local path prefix ]/template.fpp:1:21, - }, - }, - params: [ - TemplateParamConstant { - name: Name { - data: "c", - node_id: Span { - start: [ local path prefix ]/template.fpp:3:14, - end: [ local path prefix ]/template.fpp:3:15, - }, - }, - type_name: TypeName { - kind: QualIdent( - Unqualified { - name: Ident { - data: "SomeType", - node_id: Span { - start: [ local path prefix ]/template.fpp:3:17, - end: [ local path prefix ]/template.fpp:3:25, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:3:17, - end: [ local path prefix ]/template.fpp:3:25, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:3:17, - end: [ local path prefix ]/template.fpp:3:25, - }, - }, - node_id: Span { - start: [ local path prefix ]/template.fpp:3:5, - end: [ local path prefix ]/template.fpp:3:25, - }, - pre_annotation: [ - "Constant template parameter", - ], - post_annotation: [ - "Constant parameter", - ], - }, - TemplateParamType { - name: Name { - data: "TypeParam", - node_id: Span { - start: [ local path prefix ]/template.fpp:6:10, - end: [ local path prefix ]/template.fpp:6:19, - }, - }, - node_id: Span { - start: [ local path prefix ]/template.fpp:6:5, - end: [ local path prefix ]/template.fpp:6:19, - }, - pre_annotation: [ - "Type template parameter", - ], - post_annotation: [ - "Type parameter", - ], - }, - TemplateParamInterface { - name: Name { - data: "inst", - node_id: Span { - start: [ local path prefix ]/template.fpp:9:14, - end: [ local path prefix ]/template.fpp:9:18, - }, - }, - interface: Unqualified { - name: Ident { - data: "InterfaceDef", - node_id: Span { - start: [ local path prefix ]/template.fpp:9:20, - end: [ local path prefix ]/template.fpp:9:32, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:9:20, - end: [ local path prefix ]/template.fpp:9:32, - }, - }, - node_id: Span { - start: [ local path prefix ]/template.fpp:9:5, - end: [ local path prefix ]/template.fpp:9:32, - }, - pre_annotation: [ - "Interface instance template parameter", - ], - post_annotation: [ - "Instance parameter", - ], - }, - ], - members: [ - DefArray { - name: Name { - data: "Arr", - node_id: Span { - start: [ local path prefix ]/template.fpp:11:11, - end: [ local path prefix ]/template.fpp:11:14, - }, - }, - size: Expr { - kind: LiteralInt( - "3", - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:11:18, - end: [ local path prefix ]/template.fpp:11:19, - }, - }, - elt_type: TypeName { - kind: QualIdent( - Unqualified { - name: Ident { - data: "TypeParam", - node_id: Span { - start: [ local path prefix ]/template.fpp:11:21, - end: [ local path prefix ]/template.fpp:11:30, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:11:21, - end: [ local path prefix ]/template.fpp:11:30, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:11:21, - end: [ local path prefix ]/template.fpp:11:30, - }, - }, - default: None, - format: None, - is_dictionary_def: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:11:5, - end: [ local path prefix ]/template.fpp:11:30, - }, - }, - DefTopology { - is_deployment: false, - name: Name { - data: "Top", - node_id: Span { - start: [ local path prefix ]/template.fpp:13:14, - end: [ local path prefix ]/template.fpp:13:17, - }, - }, - members: [ - SpecInstance { - instance: Unqualified { - name: Ident { - data: "inst", - node_id: Span { - start: [ local path prefix ]/template.fpp:14:18, - end: [ local path prefix ]/template.fpp:14:22, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:14:18, - end: [ local path prefix ]/template.fpp:14:22, - }, - }, - node_id: Span { - start: [ local path prefix ]/template.fpp:14:9, - end: [ local path prefix ]/template.fpp:14:22, - }, - }, - ], - implements: [], - node_id: Span { - start: [ local path prefix ]/template.fpp:13:5, - end: [ local path prefix ]/template.fpp:15:6, - }, - }, - ], - node_id: Span { - start: [ local path prefix ]/template.fpp:1:1, - end: [ local path prefix ]/template.fpp:16:2, - }, - }, - SpecTemplateExpand { - template: Unqualified { - name: Ident { - data: "Tmpl", - node_id: Span { - start: [ local path prefix ]/template.fpp:19:8, - end: [ local path prefix ]/template.fpp:19:12, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:19:8, - end: [ local path prefix ]/template.fpp:19:12, - }, - }, - args: [ - TemplateArg { - kind: Constant( - Expr { - kind: LiteralInt( - "10", - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:19:22, - end: [ local path prefix ]/template.fpp:19:24, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:19:13, - end: [ local path prefix ]/template.fpp:19:24, - }, - }, - TemplateArg { - kind: Type( - TypeName { - kind: QualIdent( - Unqualified { - name: Ident { - data: "Type", - node_id: Span { - start: [ local path prefix ]/template.fpp:19:31, - end: [ local path prefix ]/template.fpp:19:35, - }, - }, - is_absolute: false, - node_id: Span { - start: [ local path prefix ]/template.fpp:19:31, - end: [ local path prefix ]/template.fpp:19:35, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:19:31, - end: [ local path prefix ]/template.fpp:19:35, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:19:26, - end: [ local path prefix ]/template.fpp:19:35, - }, - }, - TemplateArg { - kind: Interface( - Unqualified { - name: Ident { - data: "instance1", - node_id: Span { - start: [ local path prefix ]/template.fpp:19:47, - end: [ local path prefix ]/template.fpp:19:56, - }, - }, - is_absolute: true, - node_id: Span { - start: [ local path prefix ]/template.fpp:19:46, - end: [ local path prefix ]/template.fpp:19:56, - }, - }, - ), - node_id: Span { - start: [ local path prefix ]/template.fpp:19:37, - end: [ local path prefix ]/template.fpp:19:56, - }, - }, - ], - members: None, - node_id: Span { - start: [ local path prefix ]/template.fpp:19:1, - end: [ local path prefix ]/template.fpp:19:57, - }, - pre_annotation: [ - "Expansion specifier", - ], - post_annotation: [ - "Expansion specifier", - ], - }, - ], -) \ No newline at end of file diff --git a/fpp_python/python/fpp/__init__.pyi b/fpp_python/python/fpp/__init__.pyi index 91cc5768..debad9ae 100644 --- a/fpp_python/python/fpp/__init__.pyi +++ b/fpp_python/python/fpp/__init__.pyi @@ -2450,8 +2450,9 @@ def parse(paths: typing.Optional[builtins.str | builtins.list[builtins.str]] = N This is the fast front end: the nodes it yields carry locations, annotations, and children, but no resolved symbols, types, or values. Use `analyze` for - those. A `SpecTemplateExpand` is likewise left unexpanded — its `members` is - `None` — because naming the template it expands is already a symbol lookup. + those. A `SpecTemplateExpand` is likewise left unexpanded, because naming the + template it expands is already a symbol lookup: its `members` is `None` + unless the source writes the expansion out, as `expand T { ... }`. There is no `imports=` here, unlike `analyze`: nothing at this depth reads another translation unit, so every unit `parse` returns is a source. diff --git a/fpp_python/src/pipeline.rs b/fpp_python/src/pipeline.rs index 6abfaa2c..2ca86f7a 100644 --- a/fpp_python/src/pipeline.rs +++ b/fpp_python/src/pipeline.rs @@ -11,7 +11,8 @@ //! //! Template expansion does not: resolving `expand T` to the template it names //! needs a symbol table, so it belongs to the analysis and runs at [`Depth::Full`] -//! only. A `SpecTemplateExpand` from `parse` therefore has no `members`. +//! only. A `SpecTemplateExpand` from `parse` therefore has only the `members` +//! the source writes out, in the form `expand T { ... }`. use crate::diagnostics::{OwnedDiagnostic, SharedEmitter}; use crate::ir_core::{ModelData, UnitData}; @@ -230,8 +231,9 @@ fn run_off_gil( /// /// This is the fast front end: the nodes it yields carry locations, annotations, /// and children, but no resolved symbols, types, or values. Use `analyze` for -/// those. A `SpecTemplateExpand` is likewise left unexpanded — its `members` is -/// `None` — because naming the template it expands is already a symbol lookup. +/// those. A `SpecTemplateExpand` is likewise left unexpanded, because naming the +/// template it expands is already a symbol lookup: its `members` is `None` +/// unless the source writes the expansion out, as `expand T { ... }`. /// /// There is no `imports=` here, unlike `analyze`: nothing at this depth reads /// another translation unit, so every unit `parse` returns is a source. diff --git a/fpp_query/README.md b/fpp_query/README.md index 2f2f46eb..9c1d84ae 100644 --- a/fpp_query/README.md +++ b/fpp_query/README.md @@ -91,7 +91,8 @@ the same reason — a setting that quietly did nothing would drop files from the `expand` specifier, which still selects as a `SpecTemplateExpand` of its own. The body of a `module template` is never matched either way — its definitions come into being where the template is expanded, so a group reaches them only through an -expansion `templates` has materialized. +expansion `templates` has materialized, or through one the source writes out in the +form `expand T { ... }`. `templates` leaves a specifier written that way as it is. ## Template expansion diff --git a/fpp_query/src/model.rs b/fpp_query/src/model.rs index 6bc5fda9..48c3d415 100644 --- a/fpp_query/src/model.rs +++ b/fpp_query/src/model.rs @@ -66,10 +66,10 @@ pub fn parse(inputs: Vec) -> Vec { /// /// An external (bodyless) state machine declares no states, so it gets none. /// -/// Reaches a state machine written in a `module template` body, and not the members -/// an expansion specifier has materialized, so it belongs before -/// [`expand_templates`]: run in that order every copy of a template carries the enums -/// the body's state machines were given. +/// Reaches a state machine written in a `module template` body or in the members an +/// expansion specifier is written with, and not the members [`expand_templates`] +/// materializes, so it belongs before [`expand_templates`]: run in that order every +/// copy of a template carries the enums the body's state machines were given. pub fn add_state_enums(units: &mut [TransUnit]) { for unit in units { fpp_parser::add_state_enums(unit); @@ -78,6 +78,8 @@ pub fn add_state_enums(units: &mut [TransUnit]) { /// Replace each template expansion specifier with the members of the template it /// names, so that the definitions an expansion contributes are part of the model. +/// A specifier written with its members, in the form `expand T { ... }`, is already +/// expanded and keeps them. /// /// The template name is resolved syntactically, by the scoping rules for a /// qualified identifier: the innermost enclosing module outward. `fpp-check` @@ -160,6 +162,10 @@ fn expand_member( scope.pop(); } ModuleMember::SpecTemplateExpand(spec) => { + // An expansion written in the source is already expanded + if spec.members.is_some() { + return; + } let Some(def) = look_up(templates, scope, &spec.template) else { return; };