input and output types (#5750)

* input and output types

* added description

* type from stored variable

* string in custom value

* more tests with non custom
This commit is contained in:
Fernando Herrera
2022-06-10 10:59:35 -05:00
committed by GitHub
parent 9d10007085
commit d5b99ae316
12 changed files with 523 additions and 149 deletions

View File

@ -115,7 +115,7 @@ pub fn parse_for(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(b"for") {
let (call, call_span) = match working_set.find_decl(b"for", &Type::Any) {
None => {
return (
garbage(spans[0]),
@ -284,7 +284,7 @@ pub fn parse_def(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(&def_call) {
let (call, call_span) = match working_set.find_decl(&def_call, &Type::Any) {
None => {
return (
garbage_pipeline(spans),
@ -427,7 +427,7 @@ pub fn parse_extern(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(&extern_call) {
let (call, call_span) = match working_set.find_decl(&extern_call, &Type::Any) {
None => {
return (
garbage_pipeline(spans),
@ -520,7 +520,7 @@ pub fn parse_alias(
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
}
if let Some(decl_id) = working_set.find_decl(b"alias") {
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
let (call, _) = parse_internal_call(
working_set,
spans[0],
@ -622,7 +622,7 @@ pub fn parse_export(
);
};
let export_decl_id = if let Some(id) = working_set.find_decl(b"export") {
let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Any) {
id
} else {
return (
@ -655,18 +655,19 @@ pub fn parse_export(
parse_def(working_set, &lite_command, expand_aliases_denylist);
error = error.or(err);
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def") {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export def' command".into(),
export_span,
)),
);
};
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export def", &Type::Any) {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export def' command".into(),
export_span,
)),
);
};
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(Expression {
@ -690,7 +691,7 @@ pub fn parse_export(
if error.is_none() {
let decl_name = working_set.get_span_contents(spans[2]);
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
Some(Exportable::Decl(decl_id))
} else {
error = error.or_else(|| {
@ -714,19 +715,19 @@ pub fn parse_export(
parse_def(working_set, &lite_command, expand_aliases_denylist);
error = error.or(err);
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def-env")
{
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export def-env' command".into(),
export_span,
)),
);
};
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export def-env", &Type::Any) {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export def-env' command".into(),
export_span,
)),
);
};
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(Expression {
@ -750,7 +751,7 @@ pub fn parse_export(
if error.is_none() {
let decl_name = working_set.get_span_contents(spans[2]);
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
Some(Exportable::Decl(decl_id))
} else {
error = error.or_else(|| {
@ -774,18 +775,19 @@ pub fn parse_export(
parse_extern(working_set, &lite_command, expand_aliases_denylist);
error = error.or(err);
let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export extern") {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export extern' command".into(),
export_span,
)),
);
};
let export_def_decl_id =
if let Some(id) = working_set.find_decl(b"export extern", &Type::Any) {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export extern' command".into(),
export_span,
)),
);
};
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
if let Some(Expression {
@ -809,7 +811,7 @@ pub fn parse_export(
if error.is_none() {
let decl_name = working_set.get_span_contents(spans[2]);
let decl_name = trim_quotes(decl_name);
if let Some(decl_id) = working_set.find_decl(decl_name) {
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
Some(Exportable::Decl(decl_id))
} else {
error = error.or_else(|| {
@ -833,19 +835,19 @@ pub fn parse_export(
parse_alias(working_set, &lite_command.parts, expand_aliases_denylist);
error = error.or(err);
let export_alias_decl_id = if let Some(id) = working_set.find_decl(b"export alias")
{
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export alias' command".into(),
export_span,
)),
);
};
let export_alias_decl_id =
if let Some(id) = working_set.find_decl(b"export alias", &Type::Any) {
id
} else {
return (
garbage_pipeline(spans),
None,
Some(ParseError::InternalError(
"missing 'export alias' command".into(),
export_span,
)),
);
};
// Trying to warp the 'alias' call into the 'export alias' in a very clumsy way
if let Some(Expression {
@ -885,7 +887,7 @@ pub fn parse_export(
}
}
b"env" => {
if let Some(id) = working_set.find_decl(b"export env") {
if let Some(id) = working_set.find_decl(b"export env", &Type::Any) {
call.decl_id = id;
} else {
return (
@ -1190,7 +1192,7 @@ pub fn parse_module(
};
let module_decl_id = working_set
.find_decl(b"module")
.find_decl(b"module", &Type::Any)
.expect("internal error: missing module command");
let call = Box::new(Call {
@ -1239,7 +1241,7 @@ pub fn parse_use(
);
}
let (call, call_span, use_decl_id) = match working_set.find_decl(b"use") {
let (call, call_span, use_decl_id) = match working_set.find_decl(b"use", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -1472,7 +1474,7 @@ pub fn parse_hide(
);
}
let (call, call_span, hide_decl_id) = match working_set.find_decl(b"hide") {
let (call, call_span, hide_decl_id) = match working_set.find_decl(b"hide", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -1542,33 +1544,34 @@ pub fn parse_hide(
error = error.or(err);
}
let (is_module, module) =
if let Some(module_id) = working_set.find_module(&import_pattern.head.name) {
(true, working_set.get_module(module_id).clone())
} else if import_pattern.members.is_empty() {
// The pattern head can be:
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
// an alias,
let mut module = Module::new();
module.add_alias(&import_pattern.head.name, id);
let (is_module, module) = if let Some(module_id) =
working_set.find_module(&import_pattern.head.name)
{
(true, working_set.get_module(module_id).clone())
} else if import_pattern.members.is_empty() {
// The pattern head can be:
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
// an alias,
let mut module = Module::new();
module.add_alias(&import_pattern.head.name, id);
(false, module)
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name) {
// a custom command,
let mut module = Module::new();
module.add_decl(&import_pattern.head.name, id);
(false, module)
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
// a custom command,
let mut module = Module::new();
module.add_decl(&import_pattern.head.name, id);
(false, module)
} else {
// , or it could be an env var (handled by the engine)
(false, Module::new())
}
(false, module)
} else {
return (
garbage_pipeline(spans),
Some(ParseError::ModuleNotFound(spans[1])),
);
};
// , or it could be an env var (handled by the engine)
(false, Module::new())
}
} else {
return (
garbage_pipeline(spans),
Some(ParseError::ModuleNotFound(spans[1])),
);
};
// This kind of inverts the import pattern matching found in parse_use()
let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() {
@ -1696,7 +1699,7 @@ pub fn parse_overlay(
}
b"list" => {
// TODO: Abstract this code blob, it's repeated all over the place:
let call = match working_set.find_decl(b"overlay list") {
let call = match working_set.find_decl(b"overlay list", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -1755,7 +1758,7 @@ pub fn parse_overlay(
}
}
let call = match working_set.find_decl(b"overlay") {
let call = match working_set.find_decl(b"overlay", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -1820,7 +1823,7 @@ pub fn parse_overlay_new(
);
}
let (call, call_span) = match working_set.find_decl(b"overlay new") {
let (call, call_span) = match working_set.find_decl(b"overlay new", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -1911,7 +1914,7 @@ pub fn parse_overlay_add(
}
// TODO: Allow full import pattern as argument (requires custom naming of module/overlay)
let (call, call_span) = match working_set.find_decl(b"overlay add") {
let (call, call_span) = match working_set.find_decl(b"overlay add", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -2098,7 +2101,7 @@ pub fn parse_overlay_remove(
);
}
let call = match working_set.find_decl(b"overlay remove") {
let call = match working_set.find_decl(b"overlay remove", &Type::Any) {
Some(decl_id) => {
let (call, mut err) = parse_internal_call(
working_set,
@ -2209,7 +2212,7 @@ pub fn parse_let(
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
}
if let Some(decl_id) = working_set.find_decl(b"let") {
if let Some(decl_id) = working_set.find_decl(b"let", &Type::Any) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
@ -2313,7 +2316,7 @@ pub fn parse_source(
let name = working_set.get_span_contents(spans[0]);
if name == b"source" {
if let Some(decl_id) = working_set.find_decl(b"source") {
if let Some(decl_id) = working_set.find_decl(b"source", &Type::Any) {
let cwd = working_set.get_cwd();
// Is this the right call to be using here?
// Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't.
@ -2445,7 +2448,7 @@ pub fn parse_register(
// Parsing the spans and checking that they match the register signature
// Using a parsed call makes more sense than checking for how many spans are in the call
// Also, by creating a call, it can be checked if it matches the declaration signature
let (call, call_span) = match working_set.find_decl(b"register") {
let (call, call_span) = match working_set.find_decl(b"register", &Type::Any) {
None => {
return (
garbage_pipeline(spans),

View File

@ -739,7 +739,10 @@ pub fn parse_internal_call(
call.decl_id = decl_id;
call.head = command_span;
let signature = working_set.get_decl(decl_id).signature();
let decl = working_set.get_decl(decl_id);
let signature = decl.signature();
let output = decl.output_type();
working_set.found_outputs.push(output);
if signature.creates_scope {
working_set.enter_scope();
@ -1009,7 +1012,8 @@ pub fn parse_call(
pos += 1;
}
let mut maybe_decl_id = working_set.find_decl(&name);
let input = working_set.found_outputs.last().unwrap_or(&Type::Any);
let mut maybe_decl_id = working_set.find_decl(&name, input);
while maybe_decl_id.is_none() {
// Find the longest command match
@ -1031,7 +1035,7 @@ pub fn parse_call(
name.extend(name_part);
}
}
maybe_decl_id = working_set.find_decl(&name);
maybe_decl_id = working_set.find_decl(&name, input);
}
if let Some(decl_id) = maybe_decl_id {
@ -2648,7 +2652,7 @@ pub fn parse_shape_name(
);
let command_name = trim_quotes(split[1].as_bytes());
let decl_id = working_set.find_decl(command_name);
let decl_id = working_set.find_decl(command_name, &Type::Any);
if let Some(decl_id) = decl_id {
return (SyntaxShape::Custom(Box::new(shape), decl_id), err);
@ -4511,7 +4515,7 @@ pub fn parse_expression(
}
};
let with_env = working_set.find_decl(b"with-env");
let with_env = working_set.find_decl(b"with-env", &Type::Any);
if !shorthand.is_empty() {
if let Some(decl_id) = with_env {
@ -4607,7 +4611,7 @@ pub fn parse_builtin_commands(
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
b"export" => {
if let Some(decl_id) = working_set.find_decl(b"alias") {
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
let (call, _) = parse_internal_call(
working_set,
lite_command.parts[0],
@ -4818,8 +4822,9 @@ pub fn parse_block(
);
if idx == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let") {
if let Some(let_env_decl_id) = working_set.find_decl(b"let-env") {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Any) {
if let Some(let_env_decl_id) = working_set.find_decl(b"let-env", &Type::Any)
{
for expr in pipeline.expressions.iter_mut() {
if let Expression {
expr: Expr::Call(call),
@ -5136,7 +5141,7 @@ pub fn discover_captures_in_expr(
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
let span = expr.span;
if let Some(decl_id) = working_set.find_decl(b"collect") {
if let Some(decl_id) = working_set.find_decl(b"collect", &Type::Any) {
let mut output = vec![];
let var_id = working_set.next_var_id();

View File

@ -662,3 +662,291 @@ mod range {
assert!(err.is_some());
}
}
#[cfg(test)]
mod input_types {
use super::*;
use nu_protocol::{Category, Type};
#[derive(Clone)]
pub struct LsTest;
impl Command for LsTest {
fn name(&self) -> &str {
"ls"
}
fn usage(&self) -> &str {
"Mock ls command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name()).category(Category::Default)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &nu_protocol::ast::Call,
_input: nu_protocol::PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
todo!()
}
}
#[derive(Clone)]
pub struct GroupByList;
impl Command for GroupByList {
fn name(&self) -> &str {
"group-by"
}
fn usage(&self) -> &str {
"Mock group-by command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name())
.required("column", SyntaxShape::String, "column name")
.category(Category::Default)
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &nu_protocol::ast::Call,
_input: nu_protocol::PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
todo!()
}
}
#[derive(Clone)]
pub struct ToCustom;
impl Command for ToCustom {
fn name(&self) -> &str {
"to-custom"
}
fn usage(&self) -> &str {
"Mock converter command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name()).category(Category::Custom("custom".into()))
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &nu_protocol::ast::Call,
_input: nu_protocol::PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
todo!()
}
fn input_type(&self) -> nu_protocol::Type {
Type::Any
}
fn output_type(&self) -> nu_protocol::Type {
Type::Custom("custom".into())
}
}
#[derive(Clone)]
pub struct GroupByCustom;
impl Command for GroupByCustom {
fn name(&self) -> &str {
"group-by"
}
fn usage(&self) -> &str {
"Mock custom group-by command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name())
.required("column", SyntaxShape::String, "column name")
.required("other", SyntaxShape::String, "other value")
.category(Category::Custom("custom".into()))
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &nu_protocol::ast::Call,
_input: nu_protocol::PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
todo!()
}
fn input_type(&self) -> nu_protocol::Type {
Type::Custom("custom".into())
}
fn output_type(&self) -> nu_protocol::Type {
Type::Custom("custom".into())
}
}
#[derive(Clone)]
pub struct AggCustom;
impl Command for AggCustom {
fn name(&self) -> &str {
"agg"
}
fn usage(&self) -> &str {
"Mock custom agg command"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build(self.name())
.required("operation", SyntaxShape::String, "operation")
.category(Category::Custom("custom".into()))
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &nu_protocol::ast::Call,
_input: nu_protocol::PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
todo!()
}
fn input_type(&self) -> nu_protocol::Type {
Type::Custom("custom".into())
}
}
fn add_declations(engine_state: &mut EngineState) {
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(Let));
working_set.add_decl(Box::new(AggCustom));
working_set.add_decl(Box::new(GroupByCustom));
working_set.add_decl(Box::new(GroupByList));
working_set.add_decl(Box::new(LsTest));
working_set.add_decl(Box::new(ToCustom));
working_set.add_decl(Box::new(Let));
working_set.render()
};
let cwd = std::env::current_dir().expect("Could not get current working directory.");
let _ = engine_state.merge_delta(delta, None, &cwd);
}
#[test]
fn call_types_test() {
let mut engine_state = EngineState::new();
add_declations(&mut engine_state);
let mut working_set = StateWorkingSet::new(&engine_state);
let input = r#"ls | to-custom | group-by name other"#;
let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]);
assert!(err.is_none());
assert!(block.len() == 1);
let expressions = &block[0];
assert!(expressions.len() == 3);
match &expressions[0].expr {
Expr::Call(call) => {
let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
match &expressions[1].expr {
Expr::Call(call) => {
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
match &expressions[2].expr {
Expr::Call(call) => {
let expected_id = working_set
.find_decl(b"group-by", &Type::Custom("custom".into()))
.unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
}
#[test]
fn storing_variable_test() {
let mut engine_state = EngineState::new();
add_declations(&mut engine_state);
let mut working_set = StateWorkingSet::new(&engine_state);
let input =
r#"let a = (ls | to-custom | group-by name other); let b = (1+3); $a | agg sum"#;
let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]);
assert!(err.is_none());
assert!(block.len() == 3);
let expressions = &block[2];
match &expressions[1].expr {
Expr::Call(call) => {
let expected_id = working_set
.find_decl(b"agg", &Type::Custom("custom".into()))
.unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
}
#[test]
fn call_non_custom_types_test() {
let mut engine_state = EngineState::new();
add_declations(&mut engine_state);
let mut working_set = StateWorkingSet::new(&engine_state);
let input = r#"ls | group-by name"#;
let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]);
assert!(err.is_none());
assert!(block.len() == 1);
let expressions = &block[0];
assert!(expressions.len() == 2);
match &expressions[0].expr {
Expr::Call(call) => {
let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
match &expressions[1].expr {
Expr::Call(call) => {
let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap();
assert_eq!(call.decl_id, expected_id)
}
_ => panic!("Expected expression Call not found"),
}
}
}