forked from extern/nushell
Module: support defining const and use const variables inside of function (#9773)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> Relative: #8248 After this pr, user can define const variable inside a module. ![image](https://github.com/nushell/nushell/assets/22256154/e3e03e56-c4b5-4144-a944-d1b20bec1cbd) And user can export const variables, the following screenshot shows how it works (it follows https://github.com/nushell/nushell/issues/8248#issuecomment-1637442612): ![image](https://github.com/nushell/nushell/assets/22256154/b2c14760-3f27-41cc-af77-af70a4367f2a) ## About the change 1. To make module support const, we need to change `parse_module_block` to support `const` keyword. 2. To suport export `const`, we need to make module tracking variables, so we add `variables` attribute to `Module` 3. During eval, the const variable may not exists in `stack`, because we don't eval `const` when we define a module, so we need to find variables which are already registered in `engine_state` ## One more thing to note about the const value. Consider the following code ``` module foo { const b = 3; export def bar [] { $b } } use foo bar const b = 4; bar ``` The result will be 3 (which is defined in module) rather than 4. I think it's expected behavior. It's something like [dynamic binding](https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding-Tips.html) vs [lexical binding](https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html) in lisp like language, and lexical binding should be right behavior which generates more predicable result, and it doesn't introduce really subtle bugs in nushell code. What if user want dynamic-binding?(For example: the example code returns `4`) There is no way to do this, user should consider passing the value as argument to custom command rather than const. ## TODO - [X] adding tests for the feature. - [X] support export const out of module to use. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
583ef8674e
commit
f6033ac5af
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportConst;
|
||||
|
||||
impl Command for ExportConst {
|
||||
fn name(&self) -> &str {
|
||||
"export const"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use parse-time constant from a module and export them from this module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export const")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||
"equals sign followed by constant value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Re-export a command from another module",
|
||||
example: r#"module spam { export const foo = 3; }
|
||||
module eggs { export use spam foo }
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::test_int(3)),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["reexport", "import", "module"]
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ mod echo;
|
||||
mod error_make;
|
||||
mod export;
|
||||
mod export_alias;
|
||||
mod export_const;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_extern;
|
||||
@ -50,6 +51,7 @@ pub use echo::Echo;
|
||||
pub use error_make::ErrorMake;
|
||||
pub use export::ExportCommand;
|
||||
pub use export_alias::ExportAlias;
|
||||
pub use export_const::ExportConst;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_extern::ExportExtern;
|
||||
|
@ -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};
|
||||
use nu_protocol::ast::{Call, Expr, Expression, ImportPattern, ImportPatternMember};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
Category, Example, Module, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -110,6 +110,14 @@ 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!(
|
||||
@ -162,6 +170,76 @@ 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]
|
||||
|
@ -29,6 +29,7 @@ pub fn create_default_context() -> EngineState {
|
||||
ErrorMake,
|
||||
ExportAlias,
|
||||
ExportCommand,
|
||||
ExportConst,
|
||||
ExportDef,
|
||||
ExportDefEnv,
|
||||
ExportExtern,
|
||||
|
@ -58,6 +58,14 @@ pub fn eval_call(
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
for (param_idx, param) in decl
|
||||
.signature()
|
||||
|
@ -940,9 +940,8 @@ pub fn parse_export_in_block(
|
||||
let full_name = if lite_command.parts.len() > 1 {
|
||||
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
||||
match sub {
|
||||
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module" => {
|
||||
[b"export ", sub].concat()
|
||||
}
|
||||
b"alias" | b"def" | b"def-env" | b"extern" | b"extern-wrapped" | b"use" | b"module"
|
||||
| b"const" => [b"export ", sub].concat(),
|
||||
_ => b"export".to_vec(),
|
||||
}
|
||||
} else {
|
||||
@ -1000,6 +999,7 @@ pub fn parse_export_in_block(
|
||||
match full_name.as_slice() {
|
||||
b"export alias" => parse_alias(working_set, lite_command, None),
|
||||
b"export def" | b"export def-env" => parse_def(working_set, lite_command, None),
|
||||
b"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
||||
b"export use" => {
|
||||
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
||||
pipeline
|
||||
@ -1400,6 +1400,59 @@ pub fn parse_export_in_module(
|
||||
|
||||
result
|
||||
}
|
||||
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") {
|
||||
id
|
||||
} else {
|
||||
working_set.error(ParseError::InternalError(
|
||||
"missing 'export const' command".into(),
|
||||
export_span,
|
||||
));
|
||||
return (garbage_pipeline(spans), vec![]);
|
||||
};
|
||||
|
||||
// Trying to warp the 'const' call into the 'export const' in a very clumsy way
|
||||
if let Some(PipelineElement::Expression(
|
||||
_,
|
||||
Expression {
|
||||
expr: Expr::Call(ref def_call),
|
||||
..
|
||||
},
|
||||
)) = pipeline.elements.get(0)
|
||||
{
|
||||
call = def_call.clone();
|
||||
|
||||
call.head = span(&spans[0..=1]);
|
||||
call.decl_id = export_def_decl_id;
|
||||
} else {
|
||||
working_set.error(ParseError::InternalError(
|
||||
"unexpected output from parsing a definition".into(),
|
||||
span(&spans[1..]),
|
||||
));
|
||||
};
|
||||
|
||||
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(decl_id) = working_set.find_variable(decl_name) {
|
||||
result.push(Exportable::VarDecl {
|
||||
name: decl_name.to_vec(),
|
||||
id: decl_id,
|
||||
});
|
||||
} else {
|
||||
working_set.error(ParseError::InternalError(
|
||||
"failed to find added variable".into(),
|
||||
span(&spans[1..]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
_ => {
|
||||
working_set.error(ParseError::Expected(
|
||||
"def, def-env, alias, use, module, or extern keyword",
|
||||
@ -1589,6 +1642,9 @@ pub fn parse_module_block(
|
||||
None, // using commands named as the module locally is OK
|
||||
))
|
||||
}
|
||||
b"const" => block
|
||||
.pipelines
|
||||
.push(parse_const(working_set, &command.parts)),
|
||||
b"extern" | b"extern-wrapped" => {
|
||||
block
|
||||
.pipelines
|
||||
@ -1713,6 +1769,9 @@ pub fn parse_module_block(
|
||||
module.add_submodule(name, id);
|
||||
}
|
||||
}
|
||||
Exportable::VarDecl { name, id } => {
|
||||
module.add_variable(name, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1730,7 +1789,7 @@ pub fn parse_module_block(
|
||||
}
|
||||
_ => {
|
||||
working_set.error(ParseError::ExpectedKeyword(
|
||||
"def, def-env, extern, alias, use, module, export or export-env keyword".into(),
|
||||
"def, const, def-env, extern, alias, use, module, export or export-env keyword".into(),
|
||||
command.parts[0],
|
||||
));
|
||||
|
||||
@ -2208,7 +2267,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||
return (garbage_pipeline(spans), vec![]);
|
||||
};
|
||||
|
||||
let (import_pattern, module, module_id) = if let Some(module_id) = import_pattern.head.id {
|
||||
let (mut import_pattern, module, module_id) = if let Some(module_id) = import_pattern.head.id {
|
||||
let module = working_set.get_module(module_id).clone();
|
||||
(
|
||||
ImportPattern {
|
||||
@ -2219,6 +2278,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||
},
|
||||
members: import_pattern.members,
|
||||
hidden: HashSet::new(),
|
||||
module_name_var_id: None,
|
||||
},
|
||||
module,
|
||||
module_id,
|
||||
@ -2239,6 +2299,7 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||
},
|
||||
members: import_pattern.members,
|
||||
hidden: HashSet::new(),
|
||||
module_name_var_id: None,
|
||||
},
|
||||
module,
|
||||
module_id,
|
||||
@ -2276,12 +2337,29 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||
id: *module_id,
|
||||
}),
|
||||
)
|
||||
.chain(
|
||||
definitions
|
||||
.variables
|
||||
.iter()
|
||||
.map(|(name, variable_id)| Exportable::VarDecl {
|
||||
name: name.clone(),
|
||||
id: *variable_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);
|
||||
|
||||
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),
|
||||
@ -2703,7 +2781,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(ResolvedImportPattern::new(vec![], vec![]), vec![])
|
||||
(ResolvedImportPattern::new(vec![], vec![], vec![]), vec![])
|
||||
};
|
||||
|
||||
if errors.is_empty() {
|
||||
|
@ -2976,6 +2976,7 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
||||
},
|
||||
members: vec![],
|
||||
hidden: HashSet::new(),
|
||||
module_name_var_id: None,
|
||||
};
|
||||
|
||||
if spans.len() > 1 {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{span, ModuleId, Span};
|
||||
use crate::{span, ModuleId, Span, VarId};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -24,6 +24,7 @@ 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<Vec<u8>>,
|
||||
pub module_name_var_id: Option<VarId>,
|
||||
}
|
||||
|
||||
impl ImportPattern {
|
||||
@ -36,6 +37,7 @@ impl ImportPattern {
|
||||
},
|
||||
members: vec![],
|
||||
hidden: HashSet::new(),
|
||||
module_name_var_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +64,7 @@ impl ImportPattern {
|
||||
head: self.head,
|
||||
members: self.members,
|
||||
hidden,
|
||||
module_name_var_id: self.module_name_var_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1161,6 +1161,17 @@ impl<'a> StateWorkingSet<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_variables(&mut self, variables: Vec<(Vec<u8>, VarId)>) {
|
||||
let overlay_frame = self.last_overlay_mut();
|
||||
|
||||
for (mut name, var_id) in variables {
|
||||
if !name.starts_with(b"$") {
|
||||
name.insert(0, b'$');
|
||||
}
|
||||
overlay_frame.insert_variable(name, var_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
||||
let name = decl.name().as_bytes().to_vec();
|
||||
|
||||
@ -1530,11 +1541,15 @@ impl<'a> StateWorkingSet<'a> {
|
||||
}
|
||||
|
||||
pub fn find_variable(&self, name: &[u8]) -> Option<VarId> {
|
||||
let mut name = name.to_vec();
|
||||
if !name.starts_with(b"$") {
|
||||
name.insert(0, b'$');
|
||||
}
|
||||
let mut removed_overlays = vec![];
|
||||
|
||||
for scope_frame in self.delta.scope.iter().rev() {
|
||||
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
||||
if let Some(var_id) = overlay_frame.vars.get(name) {
|
||||
if let Some(var_id) = overlay_frame.vars.get(&name) {
|
||||
return Some(*var_id);
|
||||
}
|
||||
}
|
||||
@ -1545,7 +1560,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||
.active_overlays(&removed_overlays)
|
||||
.rev()
|
||||
{
|
||||
if let Some(var_id) = overlay_frame.vars.get(name) {
|
||||
if let Some(var_id) = overlay_frame.vars.get(&name) {
|
||||
return Some(*var_id);
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +206,10 @@ impl OverlayFrame {
|
||||
self.modules.insert(name, module_id)
|
||||
}
|
||||
|
||||
pub fn insert_variable(&mut self, name: Vec<u8>, variable_id: VarId) -> Option<VarId> {
|
||||
self.vars.insert(name, variable_id)
|
||||
}
|
||||
|
||||
pub fn get_decl(&self, name: &[u8]) -> Option<DeclId> {
|
||||
self.decls.get(name).cloned()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{DeclId, ModuleId};
|
||||
use crate::{DeclId, ModuleId, VarId};
|
||||
|
||||
pub enum Exportable {
|
||||
Decl { name: Vec<u8>, id: DeclId },
|
||||
Module { name: Vec<u8>, id: ModuleId },
|
||||
VarDecl { name: Vec<u8>, id: VarId },
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span,
|
||||
VarId,
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
@ -7,11 +8,20 @@ use indexmap::IndexMap;
|
||||
pub struct ResolvedImportPattern {
|
||||
pub decls: Vec<(Vec<u8>, DeclId)>,
|
||||
pub modules: Vec<(Vec<u8>, ModuleId)>,
|
||||
pub variables: Vec<(Vec<u8>, VarId)>,
|
||||
}
|
||||
|
||||
impl ResolvedImportPattern {
|
||||
pub fn new(decls: Vec<(Vec<u8>, DeclId)>, modules: Vec<(Vec<u8>, ModuleId)>) -> Self {
|
||||
ResolvedImportPattern { decls, modules }
|
||||
pub fn new(
|
||||
decls: Vec<(Vec<u8>, DeclId)>,
|
||||
modules: Vec<(Vec<u8>, ModuleId)>,
|
||||
variables: Vec<(Vec<u8>, VarId)>,
|
||||
) -> Self {
|
||||
ResolvedImportPattern {
|
||||
decls,
|
||||
modules,
|
||||
variables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +31,7 @@ pub struct Module {
|
||||
pub name: Vec<u8>,
|
||||
pub decls: IndexMap<Vec<u8>, DeclId>,
|
||||
pub submodules: IndexMap<Vec<u8>, ModuleId>,
|
||||
pub variables: IndexMap<Vec<u8>, VarId>,
|
||||
pub env_block: Option<BlockId>, // `export-env { ... }` block
|
||||
pub main: Option<DeclId>, // `export def main`
|
||||
pub span: Option<Span>,
|
||||
@ -32,6 +43,7 @@ impl Module {
|
||||
name,
|
||||
decls: IndexMap::new(),
|
||||
submodules: IndexMap::new(),
|
||||
variables: IndexMap::new(),
|
||||
env_block: None,
|
||||
main: None,
|
||||
span: None,
|
||||
@ -43,6 +55,7 @@ impl Module {
|
||||
name,
|
||||
decls: IndexMap::new(),
|
||||
submodules: IndexMap::new(),
|
||||
variables: IndexMap::new(),
|
||||
env_block: None,
|
||||
main: None,
|
||||
span: Some(span),
|
||||
@ -61,6 +74,10 @@ impl Module {
|
||||
self.submodules.insert(name, module_id)
|
||||
}
|
||||
|
||||
pub fn add_variable(&mut self, name: Vec<u8>, var_id: VarId) -> Option<VarId> {
|
||||
self.variables.insert(name, var_id)
|
||||
}
|
||||
|
||||
pub fn add_env_block(&mut self, block_id: BlockId) {
|
||||
self.env_block = Some(block_id);
|
||||
}
|
||||
@ -87,7 +104,8 @@ impl Module {
|
||||
(head, rest)
|
||||
} else {
|
||||
// Import pattern was just name without any members
|
||||
let mut results = vec![];
|
||||
let mut decls = vec![];
|
||||
let mut vars = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for (_, id) in &self.submodules {
|
||||
@ -101,14 +119,19 @@ impl Module {
|
||||
new_name.push(b' ');
|
||||
new_name.extend(sub_name);
|
||||
|
||||
results.push((new_name, sub_decl_id));
|
||||
decls.push((new_name, sub_decl_id));
|
||||
}
|
||||
|
||||
for (sub_name, sub_var_id) in sub_results.variables {
|
||||
vars.push((sub_name, sub_var_id));
|
||||
}
|
||||
}
|
||||
|
||||
results.extend(self.decls_with_head(&final_name));
|
||||
decls.extend(self.decls_with_head(&final_name));
|
||||
vars.extend(self.vars());
|
||||
|
||||
return (
|
||||
ResolvedImportPattern::new(results, vec![(final_name, self_id)]),
|
||||
ResolvedImportPattern::new(decls, vec![(final_name, self_id)], vars),
|
||||
errors,
|
||||
);
|
||||
};
|
||||
@ -118,18 +141,27 @@ impl Module {
|
||||
if name == b"main" {
|
||||
if let Some(main_decl_id) = self.main {
|
||||
(
|
||||
ResolvedImportPattern::new(vec![(final_name, main_decl_id)], vec![]),
|
||||
ResolvedImportPattern::new(
|
||||
vec![(final_name, main_decl_id)],
|
||||
vec![],
|
||||
vec![],
|
||||
),
|
||||
vec![],
|
||||
)
|
||||
} else {
|
||||
(
|
||||
ResolvedImportPattern::new(vec![], vec![]),
|
||||
ResolvedImportPattern::new(vec![], vec![], vec![]),
|
||||
vec![ParseError::ExportNotFound(*span)],
|
||||
)
|
||||
}
|
||||
} else if let Some(decl_id) = self.decls.get(name) {
|
||||
(
|
||||
ResolvedImportPattern::new(vec![(name.clone(), *decl_id)], vec![]),
|
||||
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(submodule_id) = self.submodules.get(name) {
|
||||
@ -137,7 +169,7 @@ impl Module {
|
||||
submodule.resolve_import_pattern(working_set, *submodule_id, rest, None)
|
||||
} else {
|
||||
(
|
||||
ResolvedImportPattern::new(vec![], vec![]),
|
||||
ResolvedImportPattern::new(vec![], vec![], vec![]),
|
||||
vec![ParseError::ExportNotFound(*span)],
|
||||
)
|
||||
}
|
||||
@ -145,6 +177,7 @@ impl Module {
|
||||
ImportPatternMember::Glob { .. } => {
|
||||
let mut decls = vec![];
|
||||
let mut submodules = vec![];
|
||||
let mut variables = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for (_, id) in &self.submodules {
|
||||
@ -152,18 +185,25 @@ impl Module {
|
||||
let (sub_results, sub_errors) =
|
||||
submodule.resolve_import_pattern(working_set, *id, &[], None);
|
||||
decls.extend(sub_results.decls);
|
||||
|
||||
submodules.extend(sub_results.modules);
|
||||
variables.extend(sub_results.variables);
|
||||
errors.extend(sub_errors);
|
||||
}
|
||||
|
||||
decls.extend(self.decls());
|
||||
variables.extend(self.variables.clone());
|
||||
submodules.extend(self.submodules());
|
||||
|
||||
(ResolvedImportPattern::new(decls, submodules), errors)
|
||||
(
|
||||
ResolvedImportPattern::new(decls, submodules, variables),
|
||||
errors,
|
||||
)
|
||||
}
|
||||
ImportPatternMember::List { names } => {
|
||||
let mut decls = vec![];
|
||||
let mut submodules = vec![];
|
||||
let mut variables = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for (name, span) in names {
|
||||
@ -175,6 +215,8 @@ 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(submodule_id) = self.submodules.get(name) {
|
||||
submodules.push((name.clone(), *submodule_id));
|
||||
} else {
|
||||
@ -182,7 +224,10 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
(ResolvedImportPattern::new(decls, submodules), errors)
|
||||
(
|
||||
ResolvedImportPattern::new(decls, submodules, variables),
|
||||
errors,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,6 +262,13 @@ impl Module {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn vars(&self) -> Vec<(Vec<u8>, VarId)> {
|
||||
self.variables
|
||||
.iter()
|
||||
.map(|(name, id)| (name.to_vec(), *id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn decl_names_with_head(&self, head: &[u8]) -> Vec<Vec<u8>> {
|
||||
let mut result: Vec<Vec<u8>> = self
|
||||
.decls
|
||||
|
@ -111,3 +111,47 @@ fn export_alias() -> TestResult {
|
||||
"hello",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_consts() -> TestResult {
|
||||
run_test(
|
||||
r#"module spam { export const b = 3; }; use spam b; $b"#,
|
||||
"3",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn func_use_consts() -> TestResult {
|
||||
run_test(
|
||||
r#"module spam { const b = 3; export def c [] { $b } }; use spam; spam c"#,
|
||||
"3",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_module_which_defined_const() -> TestResult {
|
||||
run_test(
|
||||
r#"module spam { export const b = 3; export const c = 4 }; use spam; $spam.b + $spam.c"#,
|
||||
"7",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_export_private_const() -> TestResult {
|
||||
fail_test(
|
||||
r#"module spam { const b = 3; export const c = 4 }; use spam; $spam.b + $spam.c"#,
|
||||
"cannot find column 'b'",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lexical_binding() -> TestResult {
|
||||
run_test(
|
||||
r#"module spam { const b = 3; export def c [] { $b } }; use spam c; const b = 4; c"#,
|
||||
"3",
|
||||
)?;
|
||||
run_test(
|
||||
r#"const b = 4; module spam { const b = 3; export def c [] { $b } }; use spam; spam c"#,
|
||||
"3",
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user