Allow overlays to import prefixed definitions (#6301)

* WIP

* Fix overlay prefix not preserving correctly

* Work around failing REPL tests

* Remove wrong code when removing with --keep-custom
This commit is contained in:
Jakub Žádník 2022-08-12 21:06:51 +03:00 committed by GitHub
parent d885258dc7
commit c3efb12733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 40 deletions

View File

@ -24,12 +24,11 @@ impl Command for OverlayAdd {
SyntaxShape::String, SyntaxShape::String,
"Module name to create overlay for", "Module name to create overlay for",
) )
// TODO: .switch(
// .switch( "prefix",
// "prefix", "Prepend module name to the imported commands and aliases",
// "Prepend module name to the imported symbols", Some('p'),
// Some('p'), )
// )
.category(Category::Core) .category(Category::Core)
} }
@ -122,13 +121,22 @@ impl Command for OverlayAdd {
Example { Example {
description: "Create an overlay from a module", description: "Create an overlay from a module",
example: r#"module spam { export def foo [] { "foo" } } example: r#"module spam { export def foo [] { "foo" } }
overlay add spam"#, overlay add spam
foo"#,
result: None,
},
Example {
description: "Create an overlay with a prefix",
example: r#"echo 'export def foo { "foo" }'
overlay add --prefix spam
spam foo"#,
result: None, result: None,
}, },
Example { Example {
description: "Create an overlay from a file", description: "Create an overlay from a file",
example: r#"echo 'export env FOO { "foo" }' | save spam.nu example: r#"echo 'export env FOO { "foo" }' | save spam.nu
overlay add spam.nu"#, overlay add spam.nu
$env.FOO"#,
result: None, result: None,
}, },
] ]

View File

@ -124,6 +124,18 @@ pub enum ParseError {
#[diagnostic(code(nu::parser::active_overlay_not_found), url(docsrs))] #[diagnostic(code(nu::parser::active_overlay_not_found), url(docsrs))]
ActiveOverlayNotFound(#[label = "not an active overlay"] Span), ActiveOverlayNotFound(#[label = "not an active overlay"] Span),
#[error("Overlay prefix mismatch.")]
#[diagnostic(
code(nu::parser::overlay_prefix_mismatch),
url(docsrs),
help("Overlay {0} already exists {1} a prefix. To add it again, do it {1} the --prefix flag.")
)]
OverlayPrefixMismatch(
String,
String,
#[label = "already exists {1} a prefix"] Span,
),
#[error("Module or overlay not found.")] #[error("Module or overlay not found.")]
#[diagnostic( #[diagnostic(
code(nu::parser::module_or_overlay_not_found), code(nu::parser::module_or_overlay_not_found),
@ -327,6 +339,7 @@ impl ParseError {
ParseError::ModuleNotFound(s) => *s, ParseError::ModuleNotFound(s) => *s,
ParseError::ModuleOrOverlayNotFound(s) => *s, ParseError::ModuleOrOverlayNotFound(s) => *s,
ParseError::ActiveOverlayNotFound(s) => *s, ParseError::ActiveOverlayNotFound(s) => *s,
ParseError::OverlayPrefixMismatch(_, _, s) => *s,
ParseError::CantRemoveLastOverlay(s) => *s, ParseError::CantRemoveLastOverlay(s) => *s,
ParseError::CantRemoveDefaultOverlay(_, s) => *s, ParseError::CantRemoveDefaultOverlay(_, s) => *s,
ParseError::NotFound(s) => *s, ParseError::NotFound(s) => *s,

View File

@ -702,21 +702,23 @@ pub fn parse_export(
let mut result = vec![]; let mut result = vec![];
let decl_name = working_set.get_span_contents(spans[2]); if let Some(decl_name_span) = spans.get(2) {
let decl_name = trim_quotes(decl_name); 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_decl(decl_name, &Type::Any) { if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
result.push(Exportable::Decl { result.push(Exportable::Decl {
name: decl_name.to_vec(), name: decl_name.to_vec(),
id: decl_id, id: decl_id,
}); });
} else { } else {
error = error.or_else(|| { error = error.or_else(|| {
Some(ParseError::InternalError( Some(ParseError::InternalError(
"failed to find added declaration".into(), "failed to find added declaration".into(),
span(&spans[1..]), span(&spans[1..]),
)) ))
}); });
}
} }
result result
@ -2032,7 +2034,13 @@ pub fn parse_overlay_new(
let module_id = working_set.add_module(&overlay_name, Module::new()); let module_id = working_set.add_module(&overlay_name, Module::new());
working_set.add_overlay(overlay_name.as_bytes().to_vec(), module_id, vec![], vec![]); working_set.add_overlay(
overlay_name.as_bytes().to_vec(),
module_id,
vec![],
vec![],
false,
);
(pipeline, None) (pipeline, None)
} }
@ -2118,6 +2126,8 @@ pub fn parse_overlay_add(
); );
}; };
let has_prefix = call.has_flag("prefix");
let pipeline = Pipeline::from_vec(vec![Expression { let pipeline = Pipeline::from_vec(vec![Expression {
expr: Expr::Call(call), expr: Expr::Call(call),
span: span(spans), span: span(spans),
@ -2125,15 +2135,36 @@ pub fn parse_overlay_add(
custom_completion: None, custom_completion: None,
}]); }]);
// TODO: Add support for it -- needs to play well with overlay remove
let has_prefix = false; //call.has_flag("prefix");
let cwd = working_set.get_cwd(); let cwd = working_set.get_cwd();
let mut error = None; let mut error = None;
let result = if let Some(module_id) = working_set.find_overlay_origin(overlay_name.as_bytes()) { let result = if let Some(overlay_frame) = working_set.find_overlay(overlay_name.as_bytes()) {
if has_prefix && !overlay_frame.prefixed {
return (
pipeline,
Some(ParseError::OverlayPrefixMismatch(
overlay_name,
"without".to_string(),
overlay_name_span,
)),
);
}
if !has_prefix && overlay_frame.prefixed {
return (
pipeline,
Some(ParseError::OverlayPrefixMismatch(
overlay_name,
"with".to_string(),
overlay_name_span,
)),
);
}
// Activate existing overlay // Activate existing overlay
let module_id = overlay_frame.origin;
if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) { if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) {
if module_id == new_module_id { if module_id == new_module_id {
Some((overlay_name, Module::new(), module_id)) Some((overlay_name, Module::new(), module_id))
@ -2237,6 +2268,7 @@ pub fn parse_overlay_add(
module_id, module_id,
decls_to_lay, decls_to_lay,
aliases_to_lay, aliases_to_lay,
has_prefix,
); );
} }

View File

@ -101,7 +101,11 @@ impl EngineState {
blocks: vec![], blocks: vec![],
modules: vec![Module::new()], modules: vec![Module::new()],
// make sure we have some default overlay: // make sure we have some default overlay:
scope: ScopeFrame::with_empty_overlay(DEFAULT_OVERLAY_NAME.as_bytes().to_vec(), 0), scope: ScopeFrame::with_empty_overlay(
DEFAULT_OVERLAY_NAME.as_bytes().to_vec(),
0,
false,
),
ctrlc: None, ctrlc: None,
env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]), env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]),
previous_env_vars: HashMap::new(), previous_env_vars: HashMap::new(),
@ -832,9 +836,11 @@ pub struct StateDelta {
impl StateDelta { impl StateDelta {
pub fn new(engine_state: &EngineState) -> Self { pub fn new(engine_state: &EngineState) -> Self {
let last_overlay = engine_state.last_overlay(&[]);
let scope_frame = ScopeFrame::with_empty_overlay( let scope_frame = ScopeFrame::with_empty_overlay(
engine_state.last_overlay_name(&[]).to_owned(), engine_state.last_overlay_name(&[]).to_owned(),
engine_state.last_overlay(&[]).origin, last_overlay.origin,
last_overlay.prefixed,
); );
StateDelta { StateDelta {
@ -1726,16 +1732,16 @@ impl<'a> StateWorkingSet<'a> {
self.permanent_state.has_overlay(name) self.permanent_state.has_overlay(name)
} }
pub fn find_overlay_origin(&self, name: &[u8]) -> Option<ModuleId> { pub fn find_overlay(&self, name: &[u8]) -> Option<&OverlayFrame> {
for scope_frame in self.delta.scope.iter().rev() { for scope_frame in self.delta.scope.iter().rev() {
if let Some(overlay_id) = scope_frame.find_overlay(name) { if let Some(overlay_id) = scope_frame.find_overlay(name) {
return Some(scope_frame.get_overlay(overlay_id).origin); return Some(scope_frame.get_overlay(overlay_id));
} }
} }
self.permanent_state self.permanent_state
.find_overlay(name) .find_overlay(name)
.map(|id| self.permanent_state.get_overlay(id).origin) .map(|id| self.permanent_state.get_overlay(id))
} }
pub fn last_overlay_name(&self) -> &Vec<u8> { pub fn last_overlay_name(&self) -> &Vec<u8> {
@ -1775,9 +1781,11 @@ impl<'a> StateWorkingSet<'a> {
pub fn last_overlay_mut(&mut self) -> &mut OverlayFrame { pub fn last_overlay_mut(&mut self) -> &mut OverlayFrame {
if self.delta.last_overlay_mut().is_none() { if self.delta.last_overlay_mut().is_none() {
// If there is no overlay, automatically activate the last one // If there is no overlay, automatically activate the last one
let overlay_frame = self.last_overlay();
let name = self.last_overlay_name().to_vec(); let name = self.last_overlay_name().to_vec();
let origin = self.last_overlay().origin; let origin = overlay_frame.origin;
self.add_overlay(name, origin, vec![], vec![]); let prefixed = overlay_frame.prefixed;
self.add_overlay(name, origin, vec![], vec![], prefixed);
} }
self.delta self.delta
@ -1841,6 +1849,7 @@ impl<'a> StateWorkingSet<'a> {
origin: ModuleId, origin: ModuleId,
decls: Vec<(Vec<u8>, DeclId)>, decls: Vec<(Vec<u8>, DeclId)>,
aliases: Vec<(Vec<u8>, AliasId)>, aliases: Vec<(Vec<u8>, AliasId)>,
prefixed: bool,
) { ) {
let last_scope_frame = self.delta.last_scope_frame_mut(); let last_scope_frame = self.delta.last_scope_frame_mut();
@ -1855,7 +1864,7 @@ impl<'a> StateWorkingSet<'a> {
} else { } else {
last_scope_frame last_scope_frame
.overlays .overlays
.push((name, OverlayFrame::from_origin(origin))); .push((name, OverlayFrame::from_origin(origin, prefixed)));
last_scope_frame.overlays.len() - 1 last_scope_frame.overlays.len() - 1
}; };
@ -1873,7 +1882,7 @@ impl<'a> StateWorkingSet<'a> {
pub fn remove_overlay(&mut self, name: &[u8], keep_custom: bool) { pub fn remove_overlay(&mut self, name: &[u8], keep_custom: bool) {
let last_scope_frame = self.delta.last_scope_frame_mut(); let last_scope_frame = self.delta.last_scope_frame_mut();
let removed_overlay_origin = if let Some(overlay_id) = last_scope_frame.find_overlay(name) { let maybe_module_id = if let Some(overlay_id) = last_scope_frame.find_overlay(name) {
last_scope_frame last_scope_frame
.active_overlays .active_overlays
.retain(|id| id != &overlay_id); .retain(|id| id != &overlay_id);
@ -1885,7 +1894,7 @@ impl<'a> StateWorkingSet<'a> {
.map(|id| self.permanent_state.get_overlay(id).origin) .map(|id| self.permanent_state.get_overlay(id).origin)
}; };
if let Some(module_id) = removed_overlay_origin { if let Some(module_id) = maybe_module_id {
last_scope_frame.removed_overlays.push(name.to_owned()); last_scope_frame.removed_overlays.push(name.to_owned());
if keep_custom { if keep_custom {

View File

@ -100,9 +100,9 @@ impl ScopeFrame {
} }
} }
pub fn with_empty_overlay(name: Vec<u8>, origin: ModuleId) -> Self { pub fn with_empty_overlay(name: Vec<u8>, origin: ModuleId, prefixed: bool) -> Self {
Self { Self {
overlays: vec![(name, OverlayFrame::from_origin(origin))], overlays: vec![(name, OverlayFrame::from_origin(origin, prefixed))],
active_overlays: vec![0], active_overlays: vec![0],
removed_overlays: vec![], removed_overlays: vec![],
predecls: HashMap::new(), predecls: HashMap::new(),
@ -205,10 +205,11 @@ pub struct OverlayFrame {
pub modules: HashMap<Vec<u8>, ModuleId>, pub modules: HashMap<Vec<u8>, ModuleId>,
pub visibility: Visibility, pub visibility: Visibility,
pub origin: ModuleId, // The original module the overlay was created from pub origin: ModuleId, // The original module the overlay was created from
pub prefixed: bool, // Whether the overlay has definitions prefixed with its name
} }
impl OverlayFrame { impl OverlayFrame {
pub fn from_origin(origin: ModuleId) -> Self { pub fn from_origin(origin: ModuleId, prefixed: bool) -> Self {
Self { Self {
vars: HashMap::new(), vars: HashMap::new(),
predecls: HashMap::new(), predecls: HashMap::new(),
@ -217,6 +218,7 @@ impl OverlayFrame {
modules: HashMap::new(), modules: HashMap::new(),
visibility: Visibility::new(), visibility: Visibility::new(),
origin, origin,
prefixed,
} }
} }

View File

@ -15,6 +15,102 @@ fn add_overlay() {
assert_eq!(actual_repl.out, "foo"); assert_eq!(actual_repl.out, "foo");
} }
#[test]
fn add_overlay_twice() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add spam"#,
r#"overlay add spam"#,
r#"foo"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo");
}
#[test]
fn add_prefixed_overlay() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add --prefix spam"#,
r#"spam foo"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo");
}
#[test]
fn add_prefixed_overlay_twice() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add --prefix spam"#,
r#"overlay add --prefix spam"#,
r#"spam foo"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo");
}
#[test]
fn add_prefixed_overlay_mismatch_1() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add --prefix spam"#,
r#"overlay add spam"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert!(actual.err.contains("exists with a prefix"));
// Why doesn't the REPL test work with the previous expected output
assert!(actual_repl.err.contains("overlay_prefix_mismatch"));
}
#[test]
fn add_prefixed_overlay_mismatch_2() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add spam"#,
r#"overlay add --prefix spam"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert!(actual.err.contains("exists without a prefix"));
// Why doesn't the REPL test work with the previous expected output
assert!(actual_repl.err.contains("overlay_prefix_mismatch"));
}
#[test]
fn prefixed_overlay_keeps_custom_decl() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay add --prefix spam"#,
r#"def bar [] { "bar" }"#,
r#"overlay remove --keep-custom spam"#,
r#"bar"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "bar");
assert_eq!(actual_repl.out, "bar");
}
#[test] #[test]
fn add_overlay_env() { fn add_overlay_env() {
let inp = &[ let inp = &[
@ -30,6 +126,21 @@ fn add_overlay_env() {
assert_eq!(actual_repl.out, "foo"); assert_eq!(actual_repl.out, "foo");
} }
#[test]
fn add_prefixed_overlay_env_no_prefix() {
let inp = &[
r#"module spam { export env FOO { "foo" } }"#,
r#"overlay add --prefix spam"#,
r#"$env.FOO"#,
];
let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; ")));
let actual_repl = nu!(cwd: "tests/overlays", nu_repl_code(inp));
assert_eq!(actual.out, "foo");
assert_eq!(actual_repl.out, "foo");
}
#[test] #[test]
fn add_overlay_from_file_decl() { fn add_overlay_from_file_decl() {
let inp = &[r#"overlay add samples/spam.nu"#, r#"foo"#]; let inp = &[r#"overlay add samples/spam.nu"#, r#"foo"#];