diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index bc2766bad9..094040a745 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -67,7 +67,7 @@ impl NuCompleter { ) -> Option> { let stack = self.stack.clone(); let block = self.engine_state.get_block(block_id); - let mut callee_stack = stack.gather_captures(&block.captures); + let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures); // Line if let Some(pos_arg) = block.signature.required_positional.get(0) { diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index 6eb0c4a326..c5a14f4cb4 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -68,7 +68,7 @@ impl Command for OverlayUse { let maybe_origin_module_id = if let Some(overlay_expr) = call.get_parser_info("overlay_expr") { - if let Expr::Overlay(module_id) = overlay_expr.expr { + if let Expr::Overlay(module_id) = &overlay_expr.expr { module_id } else { return Err(ShellError::NushellFailedSpanned { @@ -106,11 +106,11 @@ impl Command for OverlayUse { }; if let Some(module_id) = maybe_origin_module_id { - // Add environment variables only if: + // Add environment variables only if (determined by parser): // a) adding a new overlay // b) refreshing an active overlay (the origin module changed) - let module = engine_state.get_module(module_id); + let module = engine_state.get_module(*module_id); // Evaluate the export-env block (if any) and keep its environment if let Some(block_id) = module.env_block { @@ -122,7 +122,7 @@ impl Command for OverlayUse { )?; let block = engine_state.get_block(block_id); - let mut callee_stack = caller_stack.gather_captures(&block.captures); + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); if let Some(path) = &maybe_path { // Set the currently evaluated directory, if the argument is a valid path diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index 508cb78cbc..d8f4f8689e 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -1,8 +1,8 @@ use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env}; -use nu_protocol::ast::{Call, Expr, Expression, ImportPattern, ImportPatternMember}; +use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, Module, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -63,9 +63,24 @@ This command is a parser keyword. For details, check: }; if let Some(module_id) = import_pattern.head.id { - let module = engine_state.get_module(module_id); + // Add constants + for var_id in &import_pattern.constants { + let var = engine_state.get_var(*var_id); + + if let Some(constval) = &var.const_val { + caller_stack.add_var(*var_id, constval.clone()); + } else { + return Err(ShellError::NushellFailedSpanned { + msg: "Missing Constant".to_string(), + label: "constant not added by the parser".to_string(), + span: var.declaration_span, + }); + } + } // Evaluate the export-env block if there is one + let module = engine_state.get_module(module_id); + if let Some(block_id) = module.env_block { let block = engine_state.get_block(block_id); @@ -84,7 +99,7 @@ This command is a parser keyword. For details, check: .as_ref() .and_then(|path| path.parent().map(|p| p.to_path_buf())); - let mut callee_stack = caller_stack.gather_captures(&block.captures); + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); // If so, set the currently evaluated directory (file-relative PWD) if let Some(parent) = maybe_parent { @@ -110,14 +125,6 @@ This command is a parser keyword. For details, check: // Merge the block's environment to the current stack redirect_env(engine_state, caller_stack, &callee_stack); } - - use_variables( - engine_state, - import_pattern, - module, - caller_stack, - call.head, - ); } else { return Err(ShellError::GenericError( format!( @@ -170,76 +177,6 @@ This command is a parser keyword. For details, check: } } -fn use_variables( - engine_state: &EngineState, - import_pattern: &ImportPattern, - module: &Module, - caller_stack: &mut Stack, - head_span: Span, -) { - if !module.variables.is_empty() { - if import_pattern.members.is_empty() { - // add a record variable. - if let Some(var_id) = import_pattern.module_name_var_id { - let mut cols = vec![]; - let mut vals = vec![]; - for (var_name, var_id) in module.variables.iter() { - if let Some(val) = engine_state.get_var(*var_id).clone().const_val { - cols.push(String::from_utf8_lossy(var_name).to_string()); - vals.push(val) - } - } - caller_stack.add_var( - var_id, - Value::record(cols, vals, module.span.unwrap_or(head_span)), - ) - } - } else { - let mut have_glob = false; - for m in &import_pattern.members { - if matches!(m, ImportPatternMember::Glob { .. }) { - have_glob = true; - break; - } - } - if have_glob { - // bring all variables into scope directly. - for (_, var_id) in module.variables.iter() { - if let Some(val) = engine_state.get_var(*var_id).clone().const_val { - caller_stack.add_var(*var_id, val); - } - } - } else { - let mut members = vec![]; - for m in &import_pattern.members { - match m { - ImportPatternMember::List { names, .. } => { - for (n, _) in names { - if module.variables.contains_key(n) { - members.push(n); - } - } - } - ImportPatternMember::Name { name, .. } => { - if module.variables.contains_key(name) { - members.push(name) - } - } - ImportPatternMember::Glob { .. } => continue, - } - } - for m in members { - if let Some(var_id) = module.variables.get(m) { - if let Some(val) = engine_state.get_var(*var_id).clone().const_val { - caller_stack.add_var(*var_id, val); - } - } - } - } - } - } -} - #[cfg(test)] mod test { #[test] diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index fafd628b3f..d248e031c4 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -71,7 +71,7 @@ impl Command for SourceEnv { // Evaluate the block let block = engine_state.get_block(block_id as usize).clone(); - let mut callee_stack = caller_stack.gather_captures(&block.captures); + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); let result = eval_block_with_early_return( engine_state, diff --git a/crates/nu-command/src/hook.rs b/crates/nu-command/src/hook.rs index f07bc6263a..eebe8d923f 100644 --- a/crates/nu-command/src/hook.rs +++ b/crates/nu-command/src/hook.rs @@ -358,7 +358,7 @@ fn run_hook_block( let input = optional_input.unwrap_or_else(PipelineData::empty); - let mut callee_stack = stack.gather_captures(&block.captures); + let mut callee_stack = stack.gather_captures(engine_state, &block.captures); for (idx, PositionalArg { var_id, .. }) in block.signature.required_positional.iter().enumerate() diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 3a6dbf8292..fa5c49a396 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -356,7 +356,7 @@ fn get_converted_value( let block = engine_state.get_block(block_id); if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.gather_captures(&block.captures); + let mut stack = stack.gather_captures(engine_state, &block.captures); if let Some(var_id) = &var.var_id { stack.add_var(*var_id, orig_val.clone()); } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 4bfd2bc9f8..9568e80991 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -57,15 +57,7 @@ pub fn eval_call( } else if let Some(block_id) = decl.get_block_id() { let block = engine_state.get_block(block_id); - let mut callee_stack = caller_stack.gather_captures(&block.captures); - // When the def is defined in module, relative captured variable doesn't go into stack - // so it can't be merged to callee_stack, but the variable is defined in `engine_state` - // then, to solve the issue, we also need to try to get relative const from `engine_state` - for cap in &block.captures { - if let Some(value) = engine_state.get_var(*cap).const_val.clone() { - callee_stack.vars.push((*cap, value)) - } - } + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); for (param_idx, param) in decl .signature() diff --git a/crates/nu-engine/src/scope.rs b/crates/nu-engine/src/scope.rs index 02837f7e6a..2ebf60ef65 100644 --- a/crates/nu-engine/src/scope.rs +++ b/crates/nu-engine/src/scope.rs @@ -596,7 +596,7 @@ impl<'e, 's> ScopeData<'e, 's> { .collect(); let mut export_consts: Vec = module - .vars() + .consts() .iter() .map(|(name_bytes, var_id)| { Value::record( diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 186422f6a6..acdcffc736 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1409,7 +1409,8 @@ pub fn parse_export_in_module( } b"const" => { let pipeline = parse_const(working_set, &spans[1..]); - let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export const") { + let export_const_decl_id = if let Some(id) = working_set.find_decl(b"export const") + { id } else { working_set.error(ParseError::InternalError( @@ -1431,7 +1432,7 @@ pub fn parse_export_in_module( call = def_call.clone(); call.head = span(&spans[0..=1]); - call.decl_id = export_def_decl_id; + call.decl_id = export_const_decl_id; } else { working_set.error(ParseError::InternalError( "unexpected output from parsing a definition".into(), @@ -1441,15 +1442,19 @@ pub fn parse_export_in_module( let mut result = vec![]; - if let Some(decl_name_span) = spans.get(2) { - let decl_name = working_set.get_span_contents(*decl_name_span); - let decl_name = trim_quotes(decl_name); + if let Some(var_name_span) = spans.get(2) { + let var_name = working_set.get_span_contents(*var_name_span); + let var_name = trim_quotes(var_name); - if let Some(decl_id) = working_set.find_variable(decl_name) { - result.push(Exportable::VarDecl { - name: decl_name.to_vec(), - id: decl_id, - }); + if let Some(var_id) = working_set.find_variable(var_name) { + if let Err(err) = working_set.get_constant(var_id) { + working_set.error(err); + } else { + result.push(Exportable::VarDecl { + name: var_name.to_vec(), + id: var_id, + }); + } } else { working_set.error(ParseError::InternalError( "failed to find added variable".into(), @@ -2285,7 +2290,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline }, members: import_pattern.members, hidden: HashSet::new(), - module_name_var_id: None, + constants: vec![], }, module, module_id, @@ -2306,7 +2311,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline }, members: import_pattern.members, hidden: HashSet::new(), - module_name_var_id: None, + constants: vec![], }, module, module_id, @@ -2324,10 +2329,25 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline ); }; - let (definitions, errors) = - module.resolve_import_pattern(working_set, module_id, &import_pattern.members, None); + let (definitions, errors) = module.resolve_import_pattern( + working_set, + module_id, + &import_pattern.members, + None, + name_span, + ); + working_set.parse_errors.extend(errors); + let mut constants = vec![]; + + for (name, const_val) in definitions.constants { + let const_var_id = + working_set.add_variable(name.clone(), name_span, const_val.get_type(), false); + working_set.set_variable_const_val(const_var_id, const_val); + constants.push((name, const_var_id)); + } + let exportables = definitions .decls .iter() @@ -2345,8 +2365,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline }), ) .chain( - definitions - .variables + constants .iter() .map(|(name, variable_id)| Exportable::VarDecl { name: name.clone(), @@ -2355,18 +2374,13 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline ) .collect(); + import_pattern.constants = constants.iter().map(|(_, id)| *id).collect(); + // Extend the current scope with the module's exportables working_set.use_decls(definitions.decls); working_set.use_modules(definitions.modules); - working_set.use_variables(definitions.variables); + working_set.use_variables(constants); - let module_name_var_id = working_set.add_variable( - module.name(), - module.span.unwrap_or(Span::unknown()), - Type::Any, - false, - ); - import_pattern.module_name_var_id = Some(module_name_var_id); // Create a new Use command call to pass the import pattern as parser info let import_pattern_expr = Expression { expr: Expr::ImportPattern(import_pattern), @@ -2776,6 +2790,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box) -> origin_module_id, &[], Some(final_overlay_name.as_bytes()), + call.head, ) } else { origin_module.resolve_import_pattern( @@ -2785,6 +2800,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box) -> span: overlay_name_span, }], Some(final_overlay_name.as_bytes()), + call.head, ) } } else { @@ -3025,15 +3041,6 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin // const x = 'f', = at least start from index 2 if item == b"=" && spans.len() > (span.0 + 1) && span.0 > 1 { let mut idx = span.0; - // let rvalue = parse_multispan_value( - // working_set, - // spans, - // &mut idx, - // &SyntaxShape::Keyword( - // b"=".to_vec(), - // Box::new(SyntaxShape::MathExpression), - // ), - // ); let rvalue = parse_multispan_value( working_set, @@ -3099,7 +3106,6 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin // Assign the constant value to the variable working_set.set_variable_const_val(var_id, val); - // working_set.add_constant(var_id, val); } Err(err) => working_set.error(err), } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index f88a93dfa3..0b33085d45 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2977,7 +2977,7 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) - }, members: vec![], hidden: HashSet::new(), - module_name_var_id: None, + constants: vec![], }; if spans.len() > 1 { diff --git a/crates/nu-protocol/src/ast/import_pattern.rs b/crates/nu-protocol/src/ast/import_pattern.rs index 3adb144935..6a9553fa0f 100644 --- a/crates/nu-protocol/src/ast/import_pattern.rs +++ b/crates/nu-protocol/src/ast/import_pattern.rs @@ -24,7 +24,8 @@ pub struct ImportPattern { // communicate to eval which decls/aliases were hidden during `parse_hide()` so it does not // interpret these as env var names: pub hidden: HashSet>, - pub module_name_var_id: Option, + // information for the eval which const values to put into stack as variables + pub constants: Vec, } impl ImportPattern { @@ -37,7 +38,7 @@ impl ImportPattern { }, members: vec![], hidden: HashSet::new(), - module_name_var_id: None, + constants: vec![], } } @@ -64,7 +65,7 @@ impl ImportPattern { head: self.head, members: self.members, hidden, - module_name_var_id: self.module_name_var_id, + constants: self.constants, } } } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9c1338cd4c..6262ea4d5a 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -2,9 +2,10 @@ use fancy_regex::Regex; use lru::LruCache; use super::{Command, EnvVars, OverlayFrame, ScopeFrame, Stack, Visibility, DEFAULT_OVERLAY_NAME}; +use crate::ast::Block; use crate::{ - ast::Block, BlockId, Config, DeclId, Example, FileId, Module, ModuleId, OverlayId, ShellError, - Signature, Span, Type, VarId, Variable, VirtualPathId, + BlockId, Config, DeclId, Example, FileId, Module, ModuleId, OverlayId, ShellError, Signature, + Span, Type, VarId, Variable, VirtualPathId, }; use crate::{Category, ParseError, Value}; use core::panic; @@ -1674,6 +1675,19 @@ impl<'a> StateWorkingSet<'a> { } } + pub fn get_constant(&self, var_id: VarId) -> Result<&Value, ParseError> { + let var = self.get_variable(var_id); + + if let Some(const_val) = &var.const_val { + Ok(const_val) + } else { + Err(ParseError::InternalError( + "constant does not have a constant value".into(), + var.declaration_span, + )) + } + } + #[allow(clippy::borrowed_box)] pub fn get_decl(&self, decl_id: DeclId) -> &Box { let num_permanent_decls = self.permanent_state.num_decls(); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 78e1ea5fd7..1f9e3628fc 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -199,7 +199,7 @@ impl Stack { } } - pub fn gather_captures(&self, captures: &[VarId]) -> Stack { + pub fn gather_captures(&self, engine_state: &EngineState, captures: &[VarId]) -> Stack { let mut vars = vec![]; let fake_span = Span::new(0, 0); @@ -209,6 +209,8 @@ impl Stack { // that take in a var decl will manually set this into scope when running the blocks if let Ok(value) = self.get_var(*capture, fake_span) { vars.push((*capture, value)); + } else if let Some(const_val) = &engine_state.get_var(*capture).const_val { + vars.push((*capture, const_val.clone())); } } diff --git a/crates/nu-protocol/src/module.rs b/crates/nu-protocol/src/module.rs index dad724e492..fab723f173 100644 --- a/crates/nu-protocol/src/module.rs +++ b/crates/nu-protocol/src/module.rs @@ -1,6 +1,6 @@ use crate::{ ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span, - VarId, + Value, VarId, }; use indexmap::IndexMap; @@ -8,19 +8,19 @@ use indexmap::IndexMap; pub struct ResolvedImportPattern { pub decls: Vec<(Vec, DeclId)>, pub modules: Vec<(Vec, ModuleId)>, - pub variables: Vec<(Vec, VarId)>, + pub constants: Vec<(Vec, Value)>, } impl ResolvedImportPattern { pub fn new( decls: Vec<(Vec, DeclId)>, modules: Vec<(Vec, ModuleId)>, - variables: Vec<(Vec, VarId)>, + constants: Vec<(Vec, Value)>, ) -> Self { ResolvedImportPattern { decls, modules, - variables, + constants, } } } @@ -31,7 +31,7 @@ pub struct Module { pub name: Vec, pub decls: IndexMap, DeclId>, pub submodules: IndexMap, ModuleId>, - pub variables: IndexMap, VarId>, + pub constants: IndexMap, VarId>, pub env_block: Option, // `export-env { ... }` block pub main: Option, // `export def main` pub span: Option, @@ -43,7 +43,7 @@ impl Module { name, decls: IndexMap::new(), submodules: IndexMap::new(), - variables: IndexMap::new(), + constants: IndexMap::new(), env_block: None, main: None, span: None, @@ -55,7 +55,7 @@ impl Module { name, decls: IndexMap::new(), submodules: IndexMap::new(), - variables: IndexMap::new(), + constants: IndexMap::new(), env_block: None, main: None, span: Some(span), @@ -75,7 +75,7 @@ impl Module { } pub fn add_variable(&mut self, name: Vec, var_id: VarId) -> Option { - self.variables.insert(name, var_id) + self.constants.insert(name, var_id) } pub fn add_env_block(&mut self, block_id: BlockId) { @@ -95,8 +95,8 @@ impl Module { working_set: &StateWorkingSet, self_id: ModuleId, members: &[ImportPatternMember], - name_override: Option<&[u8]>, // name under the module was stored (doesn't have to be the - // same as self.name) + name_override: Option<&[u8]>, // name under the module was stored (doesn't have to be the same as self.name) + backup_span: Span, ) -> (ResolvedImportPattern, Vec) { let final_name = name_override.unwrap_or(&self.name).to_vec(); @@ -105,13 +105,15 @@ impl Module { } else { // Import pattern was just name without any members let mut decls = vec![]; - let mut vars = vec![]; + let mut const_rows = vec![]; let mut errors = vec![]; for (_, id) in &self.submodules { let submodule = working_set.get_module(*id); + let span = submodule.span.or(self.span).unwrap_or(backup_span); + let (sub_results, sub_errors) = - submodule.resolve_import_pattern(working_set, *id, &[], None); + submodule.resolve_import_pattern(working_set, *id, &[], None, span); errors.extend(sub_errors); for (sub_name, sub_decl_id) in sub_results.decls { @@ -122,16 +124,35 @@ impl Module { decls.push((new_name, sub_decl_id)); } - for (sub_name, sub_var_id) in sub_results.variables { - vars.push((sub_name, sub_var_id)); - } + const_rows.extend(sub_results.constants); } decls.extend(self.decls_with_head(&final_name)); - vars.extend(self.vars()); + + for (name, var_id) in self.consts() { + match working_set.get_constant(var_id) { + Ok(const_val) => const_rows.push((name, const_val.clone())), + Err(err) => errors.push(err), + } + } + + let mut const_cols = vec![]; + let mut const_vals = vec![]; + + for (name, val) in const_rows { + const_cols.push(String::from_utf8_lossy(&name).to_string()); + const_vals.push(val); + } + + let span = self.span.unwrap_or(backup_span); + let const_record = Value::record(const_cols, const_vals, span); return ( - ResolvedImportPattern::new(decls, vec![(final_name, self_id)], vars), + ResolvedImportPattern::new( + decls, + vec![(final_name.clone(), self_id)], + vec![(final_name, const_record)], + ), errors, ); }; @@ -159,14 +180,30 @@ impl Module { ResolvedImportPattern::new(vec![(name.clone(), *decl_id)], vec![], vec![]), vec![], ) - } else if let Some(var_id) = self.variables.get(name) { - ( - ResolvedImportPattern::new(vec![], vec![], vec![(name.clone(), *var_id)]), - vec![], - ) + } else if let Some(var_id) = self.constants.get(name) { + match working_set.get_constant(*var_id) { + Ok(const_val) => ( + ResolvedImportPattern::new( + vec![], + vec![], + vec![(name.clone(), const_val.clone())], + ), + vec![], + ), + Err(err) => ( + ResolvedImportPattern::new(vec![], vec![], vec![]), + vec![err], + ), + } } else if let Some(submodule_id) = self.submodules.get(name) { let submodule = working_set.get_module(*submodule_id); - submodule.resolve_import_pattern(working_set, *submodule_id, rest, None) + submodule.resolve_import_pattern( + working_set, + *submodule_id, + rest, + None, + self.span.unwrap_or(backup_span), + ) } else { ( ResolvedImportPattern::new(vec![], vec![], vec![]), @@ -177,33 +214,46 @@ impl Module { ImportPatternMember::Glob { .. } => { let mut decls = vec![]; let mut submodules = vec![]; - let mut variables = vec![]; + let mut constants = vec![]; let mut errors = vec![]; for (_, id) in &self.submodules { let submodule = working_set.get_module(*id); - let (sub_results, sub_errors) = - submodule.resolve_import_pattern(working_set, *id, &[], None); + let (sub_results, sub_errors) = submodule.resolve_import_pattern( + working_set, + *id, + &[], + None, + self.span.unwrap_or(backup_span), + ); decls.extend(sub_results.decls); submodules.extend(sub_results.modules); - variables.extend(sub_results.variables); + constants.extend(sub_results.constants); errors.extend(sub_errors); } decls.extend(self.decls()); - variables.extend(self.variables.clone()); + constants.extend(self.constants.iter().filter_map(|(name, var_id)| { + match working_set.get_constant(*var_id) { + Ok(const_val) => Some((name.clone(), const_val.clone())), + Err(err) => { + errors.push(err); + None + } + } + })); submodules.extend(self.submodules()); ( - ResolvedImportPattern::new(decls, submodules, variables), + ResolvedImportPattern::new(decls, submodules, constants), errors, ) } ImportPatternMember::List { names } => { let mut decls = vec![]; - let mut submodules = vec![]; - let mut variables = vec![]; + let mut modules = vec![]; + let mut constants = vec![]; let mut errors = vec![]; for (name, span) in names { @@ -215,17 +265,32 @@ impl Module { } } else if let Some(decl_id) = self.decls.get(name) { decls.push((name.clone(), *decl_id)); - } else if let Some(var_id) = self.variables.get(name) { - variables.push((name.clone(), *var_id)); + } else if let Some(var_id) = self.constants.get(name) { + match working_set.get_constant(*var_id) { + Ok(const_val) => constants.push((name.clone(), const_val.clone())), + Err(err) => errors.push(err), + } } else if let Some(submodule_id) = self.submodules.get(name) { - submodules.push((name.clone(), *submodule_id)); + let submodule = working_set.get_module(*submodule_id); + let (sub_results, sub_errors) = submodule.resolve_import_pattern( + working_set, + *submodule_id, + rest, + None, + self.span.unwrap_or(backup_span), + ); + + decls.extend(sub_results.decls); + modules.extend(sub_results.modules); + constants.extend(sub_results.constants); + errors.extend(sub_errors); } else { errors.push(ParseError::ExportNotFound(*span)); } } ( - ResolvedImportPattern::new(decls, submodules, variables), + ResolvedImportPattern::new(decls, modules, constants), errors, ) } @@ -262,8 +327,8 @@ impl Module { result } - pub fn vars(&self) -> Vec<(Vec, VarId)> { - self.variables + pub fn consts(&self) -> Vec<(Vec, VarId)> { + self.constants .iter() .map(|(name, id)| (name.to_vec(), *id)) .collect() diff --git a/tests/const_/mod.rs b/tests/const_/mod.rs index 0de07b316d..d821e71b9f 100644 --- a/tests/const_/mod.rs +++ b/tests/const_/mod.rs @@ -1,6 +1,19 @@ use nu_test_support::nu; use pretty_assertions::assert_eq; +const MODULE_SETUP: &str = r#" + module spam { + export const X = 'x' + export module eggs { + export const E = 'e' + export module bacon { + export const viking = 'eats' + export module none {} + } + } + } +"#; + #[test] fn const_bool() { let inp = &["const x = false", "$x"]; @@ -111,3 +124,129 @@ fn const_in_scope() { assert_eq!(actual.out, "x"); } + +#[test] +fn complex_const_export() { + let inp = &[MODULE_SETUP, "use spam", "$spam.X"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "x"); + + let inp = &[MODULE_SETUP, "use spam", "$spam.eggs.E"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "e"); + + let inp = &[MODULE_SETUP, "use spam", "$spam.eggs.bacon.viking"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "eats"); + + let inp = &[ + MODULE_SETUP, + "use spam", + "($spam.eggs.bacon.none | is-empty)", + ]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "true"); +} + +#[test] +fn complex_const_glob_export() { + let inp = &[MODULE_SETUP, "use spam *", "$X"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "x"); + + let inp = &[MODULE_SETUP, "use spam *", "$eggs.E"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "e"); + + let inp = &[MODULE_SETUP, "use spam *", "$eggs.bacon.viking"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "eats"); + + let inp = &[MODULE_SETUP, "use spam *", "($eggs.bacon.none | is-empty)"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "true"); +} + +#[test] +fn complex_const_drill_export() { + let inp = &[ + MODULE_SETUP, + "use spam eggs bacon none", + "($none | is-empty)", + ]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "true"); +} + +#[test] +fn complex_const_list_export() { + let inp = &[ + MODULE_SETUP, + "use spam [X eggs]", + "[$X $eggs.E] | str join ''", + ]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "xe"); +} + +#[test] +fn exported_const_is_const() { + let module1 = "module foo { + export def main [] { 'foo' } + }"; + + let module2 = "module spam { + export const MOD_NAME = 'foo' + }"; + + let inp = &[module1, module2, "use spam", "use $spam.MOD_NAME", "foo"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "foo"); +} + +#[test] +fn const_captures_work() { + let module = "module spam { + export const X = 'x' + const Y = 'y' + + export-env { $env.SPAM = $X + $Y } + export def main [] { $X + $Y } + }"; + + let inp = &[module, "use spam", "$env.SPAM"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "xy"); + + let inp = &[module, "use spam", "spam"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "xy"); +} + +#[ignore = "TODO: Need to fix `overlay hide` to hide the constants brough by `overlay use`"] +#[test] +fn complex_const_overlay_use_hide() { + let inp = &[MODULE_SETUP, "overlay use spam", "$X"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "x"); + + let inp = &[MODULE_SETUP, "overlay use spam", "$eggs.E"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "e"); + + let inp = &[MODULE_SETUP, "overlay use spam", "$eggs.bacon.viking"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "eats"); + + let inp = &[ + MODULE_SETUP, + "overlay use spam", + "($eggs.bacon.none | is-empty)", + ]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "true"); + + let inp = &[MODULE_SETUP, "overlay use spam", "overlay hide", "$eggs"]; + let actual = nu!(&inp.join("; ")); + assert!(actual.err.contains("nu::parser::variable_not_found")); +} diff --git a/tests/modules/mod.rs b/tests/modules/mod.rs index d51c9d0dde..55a080303a 100644 --- a/tests/modules/mod.rs +++ b/tests/modules/mod.rs @@ -750,3 +750,20 @@ fn module_main_not_found() { let actual = nu!(&inp.join("; ")); assert!(actual.err.contains("export_not_found")); } + +#[test] +fn nested_list_export_works() { + let module = r#" + module spam { + export module eggs { + export def bacon [] { 'bacon' } + } + + export def sausage [] { 'sausage' } + } + "#; + + let inp = &[module, "use spam [sausage eggs]", "eggs bacon"]; + let actual = nu!(&inp.join("; ")); + assert_eq!(actual.out, "bacon"); +}