From 1134c2f16c3e621bdf26ab9b358cf7e7a4783572 Mon Sep 17 00:00:00 2001 From: StevenDoesStuffs Date: Wed, 5 Apr 2023 11:56:48 -0500 Subject: [PATCH] Allow NU_LIBS_DIR and friends to be const (#8538) --- .../src/completions/custom_completions.rs | 4 +- .../src/core_commands/overlay/use_.rs | 38 ++-- crates/nu-cmd-lang/src/core_commands/use_.rs | 13 +- crates/nu-command/src/deprecated/source.rs | 2 +- crates/nu-command/src/env/source_env.rs | 15 +- crates/nu-command/src/system/nu_check.rs | 9 +- crates/nu-engine/src/call_ext.rs | 6 +- crates/nu-engine/src/env.rs | 75 ++++---- crates/nu-engine/src/eval.rs | 4 +- crates/nu-parser/src/parse_keywords.rs | 171 ++++++++++++------ crates/nu-parser/src/parser.rs | 32 +++- crates/nu-protocol/src/ast/call.rs | 18 +- tests/shell/mod.rs | 28 +++ 13 files changed, 279 insertions(+), 136 deletions(-) diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 950685d45f..6c51a6bef1 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -6,7 +6,7 @@ use nu_protocol::{ PipelineData, Span, Type, Value, }; use reedline::Suggestion; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use super::completer::map_value_completions; @@ -66,7 +66,7 @@ impl Completer for CustomCompletion { ], redirect_stdout: true, redirect_stderr: true, - parser_info: vec![], + parser_info: HashMap::new(), }, PipelineData::empty(), ); 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 4d546abec8..e64acbae8d 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, find_in_dirs_env, redirect_env, CallExt}; +use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env, CallExt}; use nu_parser::trim_quotes_str; use nu_protocol::ast::{Call, Expr}; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -66,23 +66,24 @@ impl Command for OverlayUse { let mut name_arg: Spanned = call.req(engine_state, caller_stack, 0)?; name_arg.item = trim_quotes_str(&name_arg.item).to_string(); - let maybe_origin_module_id = if let Some(overlay_expr) = call.parser_info_nth(0) { - if let Expr::Overlay(module_id) = overlay_expr.expr { - module_id + 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 { + module_id + } else { + return Err(ShellError::NushellFailedSpanned { + msg: "Not an overlay".to_string(), + label: "requires an overlay (path or a string)".to_string(), + span: overlay_expr.span, + }); + } } else { return Err(ShellError::NushellFailedSpanned { - msg: "Not an overlay".to_string(), - label: "requires an overlay (path or a string)".to_string(), - span: overlay_expr.span, + msg: "Missing positional".to_string(), + label: "missing required overlay".to_string(), + span: call.head, }); - } - } else { - return Err(ShellError::NushellFailedSpanned { - msg: "Missing positional".to_string(), - label: "missing required overlay".to_string(), - span: call.head, - }); - }; + }; let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? { name @@ -113,7 +114,12 @@ impl Command for OverlayUse { // Evaluate the export-env block (if any) and keep its environment if let Some(block_id) = module.env_block { - let maybe_path = find_in_dirs_env(&name_arg.item, engine_state, caller_stack)?; + let maybe_path = find_in_dirs_env( + &name_arg.item, + engine_state, + caller_stack, + get_dirs_var_from_call(call), + )?; let block = engine_state.get_block(block_id); let mut callee_stack = caller_stack.gather_captures(&block.captures); diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index 7b4afbeee2..5bfb0b9203 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, find_in_dirs_env, redirect_env}; +use nu_engine::{eval_block, find_in_dirs_env, get_dirs_var_from_call, redirect_env}; use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -48,7 +48,7 @@ impl Command for Use { let import_pattern = if let Some(Expression { expr: Expr::ImportPattern(pat), .. - }) = call.parser_info_nth(0) + }) = call.get_parser_info("import_pattern") { pat } else { @@ -72,9 +72,12 @@ impl Command for Use { let module_arg_str = String::from_utf8_lossy( engine_state.get_span_contents(&import_pattern.head.span), ); - let maybe_parent = if let Some(path) = - find_in_dirs_env(&module_arg_str, engine_state, caller_stack)? - { + let maybe_parent = if let Some(path) = find_in_dirs_env( + &module_arg_str, + engine_state, + caller_stack, + get_dirs_var_from_call(call), + )? { path.parent().map(|p| p.to_path_buf()).or(None) } else { None diff --git a/crates/nu-command/src/deprecated/source.rs b/crates/nu-command/src/deprecated/source.rs index 5a29828dd5..904b000393 100644 --- a/crates/nu-command/src/deprecated/source.rs +++ b/crates/nu-command/src/deprecated/source.rs @@ -45,7 +45,7 @@ impl Command for Source { ) -> Result { // Note: this hidden positional is the block_id that corresponded to the 0th position // it is put here by the parser - let block_id: i64 = call.req_parser_info(engine_state, stack, 0)?; + let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?; let block = engine_state.get_block(block_id as usize).clone(); eval_block_with_early_return( diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 3edabac17f..c77008b699 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -use nu_engine::{eval_block_with_early_return, find_in_dirs_env, redirect_env, CallExt}; +use nu_engine::{ + eval_block_with_early_return, find_in_dirs_env, get_dirs_var_from_call, redirect_env, CallExt, +}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -42,12 +44,15 @@ impl Command for SourceEnv { // Note: this hidden positional is the block_id that corresponded to the 0th position // it is put here by the parser - let block_id: i64 = call.req_parser_info(engine_state, caller_stack, 0)?; + let block_id: i64 = call.req_parser_info(engine_state, caller_stack, "block_id")?; // Set the currently evaluated directory (file-relative PWD) - let mut parent = if let Some(path) = - find_in_dirs_env(&source_filename.item, engine_state, caller_stack)? - { + let mut parent = if let Some(path) = find_in_dirs_env( + &source_filename.item, + engine_state, + caller_stack, + get_dirs_var_from_call(call), + )? { PathBuf::from(&path) } else { return Err(ShellError::FileNotFound(source_filename.span)); diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 2566d6c5d6..f9ea8e2203 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -1,4 +1,4 @@ -use nu_engine::{find_in_dirs_env, CallExt}; +use nu_engine::{find_in_dirs_env, get_dirs_var_from_call, CallExt}; use nu_parser::{parse, parse_module_block, unescape_unquote_string}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet}; @@ -106,7 +106,12 @@ impl Command for NuCheck { _ => { if let Some(path_str) = path { // look up the path as relative to FILE_PWD or inside NU_LIB_DIRS (same process as source-env) - let path = match find_in_dirs_env(&path_str.item, engine_state, stack) { + let path = match find_in_dirs_env( + &path_str.item, + engine_state, + stack, + get_dirs_var_from_call(call), + ) { Ok(path) => { if let Some(path) = path { path diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index 7bdab98d8d..d3c543aab6 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -39,7 +39,7 @@ pub trait CallExt { &self, engine_state: &EngineState, stack: &mut Stack, - pos: usize, + name: &str, ) -> Result; } @@ -111,9 +111,9 @@ impl CallExt for Call { &self, engine_state: &EngineState, stack: &mut Stack, - pos: usize, + name: &str, ) -> Result { - if let Some(expr) = self.parser_info_nth(pos) { + if let Some(expr) = self.get_parser_info(name) { let result = eval_expression(engine_state, stack, expr)?; FromValue::from_value(&result) } else if self.parser_info.is_empty() { diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 3a04366397..c887f5c6e1 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use nu_protocol::ast::PathMember; +use nu_protocol::ast::{Call, Expr, PathMember}; use nu_protocol::engine::{EngineState, Stack}; -use nu_protocol::{PipelineData, ShellError, Span, Value}; +use nu_protocol::{PipelineData, ShellError, Span, Value, VarId}; use nu_path::canonicalize_with; @@ -18,8 +18,6 @@ const ENV_PATH_NAME: &str = "PATH"; const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS"; -static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; - enum ConversionResult { Ok(Value), ConversionError(ShellError), // Failure during the conversion itself @@ -224,6 +222,17 @@ pub fn path_str( env_to_string(pathname, &pathval, engine_state, stack) } +pub const DIR_VAR_PARSER_INFO: &str = "dirs_var"; +pub fn get_dirs_var_from_call(call: &Call) -> Option { + call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| { + if let Expr::Var(id) = x.expr { + Some(id) + } else { + None + } + }) +} + /// This helper function is used to find files during eval /// /// First, the actual current working directory is selected as @@ -239,6 +248,7 @@ pub fn find_in_dirs_env( filename: &str, engine_state: &EngineState, stack: &Stack, + dirs_var: Option, ) -> Result, ShellError> { // Choose whether to use file-relative or PWD-relative path let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") { @@ -262,36 +272,35 @@ pub fn find_in_dirs_env( current_dir_str(engine_state, stack)? }; - if let Ok(p) = canonicalize_with(filename, &cwd) { - Ok(Some(p)) - } else { - let path = Path::new(filename); - - if path.is_relative() { - if let Some(lib_dirs) = stack.get_env_var(engine_state, LIB_DIRS_ENV) { - if let Ok(dirs) = lib_dirs.as_list() { - for lib_dir in dirs { - if let Ok(dir) = lib_dir.as_path() { - // make sure the dir is absolute path - if let Ok(dir_abs) = canonicalize_with(dir, &cwd) { - if let Ok(path) = canonicalize_with(filename, dir_abs) { - return Ok(Some(path)); - } - } - } - } - - Ok(None) - } else { - Ok(None) - } - } else { - Ok(None) - } - } else { - Ok(None) + let check_dir = |lib_dirs: Option| -> Option { + if let Ok(p) = canonicalize_with(filename, &cwd) { + return Some(p); } - } + let path = Path::new(filename); + if !path.is_relative() { + return None; + } + + lib_dirs? + .as_list() + .ok()? + .iter() + .map(|lib_dir| -> Option { + let dir = lib_dir.as_path().ok()?; + let dir_abs = canonicalize_with(dir, &cwd).ok()?; + canonicalize_with(filename, dir_abs).ok() + }) + .find(Option::is_some) + .flatten() + }; + + let lib_dirs = dirs_var + .and_then(|dirs_var| engine_state.find_constant(dirs_var, &[])) + .cloned(); + // TODO: remove (see #8310) + let lib_dirs_fallback = stack.get_env_var(engine_state, "NU_LIB_DIRS"); + + Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback))) } fn get_converted_value( diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index a07d144f36..d4a09f68d3 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -859,7 +859,7 @@ pub fn eval_element_with_input( ], redirect_stdout: false, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }, input, ) @@ -930,7 +930,7 @@ pub fn eval_element_with_input( ], redirect_stdout: false, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }, input, ) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 70b91d774e..50766beb70 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -7,13 +7,14 @@ use nu_protocol::{ }, engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, span, Alias, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type, + VarId, }; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; -static LIB_DIRS_ENV: &str = "NU_LIB_DIRS"; +pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS"; #[cfg(feature = "plugin")] -static PLUGIN_DIRS_ENV: &str = "NU_PLUGIN_DIRS"; +pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS"; use crate::{ eval::{eval_constant, value_as_string}, @@ -916,7 +917,7 @@ pub fn parse_old_alias( decl_id, redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), })); return ( Pipeline::from_vec(vec![Expression { @@ -1217,7 +1218,7 @@ pub fn parse_export_in_module( arguments: vec![], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); let exportables = if let Some(kw_span) = spans.get(1) { @@ -2041,7 +2042,7 @@ pub fn parse_module( ], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); ( @@ -2184,7 +2185,7 @@ pub fn parse_use( if err.is_none() { if let Some(module_path) = - find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) + find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_VAR) { if let Some(i) = working_set .parsed_module_files @@ -2398,7 +2399,7 @@ pub fn parse_use( }; let mut call = call; - call.add_parser_info(import_pattern_expr); + call.set_parser_info("import_pattern".to_string(), import_pattern_expr); ( Pipeline::from_vec(vec![Expression { @@ -2619,7 +2620,7 @@ pub fn parse_hide( }; let mut call = call; - call.add_parser_info(import_pattern_expr); + call.set_parser_info("import_pattern".to_string(), import_pattern_expr); ( Pipeline::from_vec(vec![Expression { @@ -2844,7 +2845,7 @@ pub fn parse_overlay_use( String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec()) { if let Some(module_path) = - find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) + find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_VAR) { let overlay_name = if let Some(stem) = module_path.file_stem() { stem.to_string_lossy().to_string() @@ -2936,16 +2937,19 @@ pub fn parse_overlay_use( // Change the call argument to include the Overlay expression with the module ID let mut call = call; - call.add_parser_info(Expression { - expr: Expr::Overlay(if is_module_updated { - Some(origin_module_id) - } else { - None - }), - span: overlay_name_span, - ty: Type::Any, - custom_completion: None, - }); + call.set_parser_info( + "overlay_expr".to_string(), + Expression { + expr: Expr::Overlay(if is_module_updated { + Some(origin_module_id) + } else { + None + }), + span: overlay_name_span, + ty: Type::Any, + custom_completion: None, + }, + ); let pipeline = Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), @@ -3117,7 +3121,7 @@ pub fn parse_let_or_const( ], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); return ( @@ -3242,7 +3246,7 @@ pub fn parse_mut( ], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), }); return ( @@ -3371,7 +3375,7 @@ pub fn parse_source( } }; - if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) { + if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_VAR) { if let Ok(contents) = std::fs::read(&path) { // Change currently parsed directory let prev_currently_parsed_cwd = if let Some(parent) = path.parent() { @@ -3417,12 +3421,15 @@ pub fn parse_source( // FIXME: Adding this expression to the positional creates a syntax highlighting error // after writing `source example.nu` - call_with_block.add_parser_info(Expression { - expr: Expr::Int(block_id as i64), - span: spans[1], - ty: Type::Any, - custom_completion: None, - }); + call_with_block.set_parser_info( + "block_id".to_string(), + Expression { + expr: Expr::Int(block_id as i64), + span: spans[1], + ty: Type::Any, + custom_completion: None, + }, + ); return ( Pipeline::from_vec(vec![Expression { @@ -3632,7 +3639,7 @@ pub fn parse_register( if let Some(err) = err { Err(err) } else { - let path = if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_ENV) + let path = if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_VAR) { p } else { @@ -3779,14 +3786,22 @@ pub fn parse_register( ) } +pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option { + working_set + .find_variable(format!("${}", var_name).as_bytes()) + .filter(|var_id| working_set.find_constant(*var_id).is_some()) +} + /// This helper function is used to find files during parsing /// /// First, the actual current working directory is selected as /// a) the directory of a file currently being parsed /// b) current working directory (PWD) /// -/// Then, if the file is not found in the actual cwd, NU_LIB_DIRS is checked. -/// If there is a relative path in NU_LIB_DIRS, it is assumed to be relative to the actual cwd +/// Then, if the file is not found in the actual cwd, dirs_var is checked. +/// For now, we first check for a const with the name of `dirs_var_name`, +/// and if that's not found, then we try to look for an environment variable of the same name. +/// If there is a relative path in dirs_var, it is assumed to be relative to the actual cwd /// determined in the first step. /// /// Always returns an absolute path @@ -3794,43 +3809,89 @@ pub fn find_in_dirs( filename: &str, working_set: &StateWorkingSet, cwd: &str, - dirs_env: &str, + dirs_var_name: &str, ) -> Option { - // Choose whether to use file-relative or PWD-relative path - let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd { - currently_parsed_cwd.as_path() - } else { - Path::new(cwd) - }; + pub fn find_in_dirs_with_id( + filename: &str, + working_set: &StateWorkingSet, + cwd: &str, + dirs_var_name: &str, + ) -> Option { + // Choose whether to use file-relative or PWD-relative path + let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd { + currently_parsed_cwd.as_path() + } else { + Path::new(cwd) + }; + if let Ok(p) = canonicalize_with(filename, actual_cwd) { + return Some(p); + } - if let Ok(p) = canonicalize_with(filename, actual_cwd) { - Some(p) - } else { let path = Path::new(filename); + if !path.is_relative() { + return None; + } - if path.is_relative() { - if let Some(lib_dirs) = working_set.get_env_var(dirs_env) { - if let Ok(dirs) = lib_dirs.as_list() { - for lib_dir in dirs { - if let Ok(dir) = lib_dir.as_path() { - // make sure the dir is absolute path - if let Ok(dir_abs) = canonicalize_with(dir, actual_cwd) { - if let Ok(path) = canonicalize_with(filename, dir_abs) { - return Some(path); + working_set + .find_constant(find_dirs_var(working_set, dirs_var_name)?)? + .as_list() + .ok()? + .iter() + .map(|lib_dir| -> Option { + let dir = lib_dir.as_path().ok()?; + let dir_abs = canonicalize_with(dir, actual_cwd).ok()?; + canonicalize_with(filename, dir_abs).ok() + }) + .find(Option::is_some) + .flatten() + } + + // TODO: remove (see #8310) + pub fn find_in_dirs_old( + filename: &str, + working_set: &StateWorkingSet, + cwd: &str, + dirs_env: &str, + ) -> Option { + // Choose whether to use file-relative or PWD-relative path + let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd { + currently_parsed_cwd.as_path() + } else { + Path::new(cwd) + }; + + if let Ok(p) = canonicalize_with(filename, actual_cwd) { + Some(p) + } else { + let path = Path::new(filename); + + if path.is_relative() { + if let Some(lib_dirs) = working_set.get_env_var(dirs_env) { + if let Ok(dirs) = lib_dirs.as_list() { + for lib_dir in dirs { + if let Ok(dir) = lib_dir.as_path() { + // make sure the dir is absolute path + if let Ok(dir_abs) = canonicalize_with(dir, actual_cwd) { + if let Ok(path) = canonicalize_with(filename, dir_abs) { + return Some(path); + } } } } - } - None + None + } else { + None + } } else { None } } else { None } - } else { - None } } + + find_in_dirs_with_id(filename, working_set, cwd, dirs_var_name) + .or_else(|| find_in_dirs_old(filename, working_set, cwd, dirs_var_name)) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5acd74976c..70377a23d8 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -8,6 +8,7 @@ use crate::{ ParseError, Token, TokenContents, }; +use nu_engine::DIR_VAR_PARSER_INFO; use nu_protocol::{ ast::{ Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression, @@ -20,10 +21,10 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl, + find_dirs_var, is_unaliasable_parser_keyword, parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for, parse_hide, parse_keyword, parse_let_or_const, parse_module, parse_old_alias, parse_overlay_hide, parse_overlay_new, parse_overlay_use, - parse_source, parse_use, parse_where, parse_where_expr, + parse_source, parse_use, parse_where, parse_where_expr, LIB_DIRS_VAR, }; use itertools::Itertools; @@ -809,6 +810,25 @@ pub struct ParsedInternalCall { pub error: Option, } +fn attach_parser_info_builtin(working_set: &StateWorkingSet, name: &str, call: &mut Call) { + match name { + "use" | "overlay use" | "source-env" | "nu-check" => { + if let Some(var_id) = find_dirs_var(working_set, LIB_DIRS_VAR) { + call.set_parser_info( + DIR_VAR_PARSER_INFO.to_owned(), + Expression { + expr: Expr::Var(var_id), + span: call.head, + ty: Type::Any, + custom_completion: None, + }, + ); + } + } + _ => {} + } +} + pub fn parse_internal_call( working_set: &mut StateWorkingSet, command_span: Span, @@ -828,6 +848,10 @@ pub fn parse_internal_call( let signature = decl.signature(); let output = signature.output_type.clone(); + if decl.is_builtin() { + attach_parser_info_builtin(working_set, decl.name(), &mut call); + } + // The index into the positional parameter in the definition let mut positional_idx = 0; @@ -5686,7 +5710,7 @@ pub fn parse_expression( arguments, redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), })); ( @@ -6577,7 +6601,7 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) decl_id, redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), })), span, ty: Type::String, diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 2ae2e9bebe..de51617ba3 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use super::Expression; @@ -19,7 +21,7 @@ pub struct Call { pub redirect_stdout: bool, pub redirect_stderr: bool, /// this field is used by the parser to pass additional command-specific information - pub parser_info: Vec, + pub parser_info: HashMap, } impl Call { @@ -30,7 +32,7 @@ impl Call { arguments: vec![], redirect_stdout: true, redirect_stderr: false, - parser_info: vec![], + parser_info: HashMap::new(), } } @@ -70,10 +72,6 @@ impl Call { self.arguments.push(Argument::Positional(positional)); } - pub fn add_parser_info(&mut self, info: Expression) { - self.parser_info.push(info); - } - pub fn add_unknown(&mut self, unknown: Expression) { self.arguments.push(Argument::Unknown(unknown)); } @@ -106,8 +104,12 @@ impl Call { self.positional_iter().count() } - pub fn parser_info_nth(&self, i: usize) -> Option<&Expression> { - self.parser_info.get(i) + pub fn get_parser_info(&self, name: &str) -> Option<&Expression> { + self.parser_info.get(name) + } + + pub fn set_parser_info(&mut self, name: String, val: Expression) -> Option { + self.parser_info.insert(name, val) } pub fn has_flag(&self, flag_name: &str) -> bool { diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs index 3031de81c5..7fb32a8baf 100644 --- a/tests/shell/mod.rs +++ b/tests/shell/mod.rs @@ -140,6 +140,34 @@ fn nu_lib_dirs_relative_repl() { }) } +// TODO: add absolute path tests after we expand const capabilities (see #8310) +#[test] +fn const_nu_lib_dirs_relative() { + Playground::setup("const_nu_lib_dirs_relative", |dirs, sandbox| { + sandbox + .mkdir("scripts") + .with_files(vec![FileWithContentToBeTrimmed( + "scripts/foo.nu", + r#" + let-env FOO = "foo" + "#, + )]) + .with_files(vec![FileWithContentToBeTrimmed( + "main.nu", + r#" + const NU_LIB_DIRS = [ 'scripts' ] + source-env foo.nu + $env.FOO + "#, + )]); + + let outcome = nu!(cwd: dirs.test(), "source main.nu"); + + assert!(outcome.err.is_empty()); + assert_eq!(outcome.out, "foo"); + }) +} + #[test] fn nu_lib_dirs_relative_script() { Playground::setup("nu_lib_dirs_relative_script", |dirs, sandbox| {