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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 523 additions and 149 deletions

View File

@ -4,6 +4,7 @@ use log::trace;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_engine::convert_env_values; use nu_engine::convert_env_values;
use nu_parser::parse; use nu_parser::parse;
use nu_protocol::Type;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
@ -34,7 +35,7 @@ pub fn evaluate_file(
let _ = parse(&mut working_set, Some(&path), &file, false, &[]); let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
if working_set.find_decl(b"main").is_some() { if working_set.find_decl(b"main", &Type::Any).is_some() {
let args = format!("main {}", args.join(" ")); let args = format!("main {}", args.join(" "));
if !eval_source( if !eval_source(

View File

@ -91,7 +91,7 @@ impl CustomValue for ExprDb {
Value::Bool { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Boolean(*val))), Value::Bool { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Boolean(*val))),
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span: op, op_span: op,
lhs_ty: Type::Custom, lhs_ty: Type::Custom(self.typetag_name().into()),
lhs_span, lhs_span,
rhs_ty: right.get_type(), rhs_ty: right.get_type(),
rhs_span: right.span()?, rhs_span: right.span()?,

View File

@ -126,9 +126,9 @@ fn with_operator(
.into_value(lhs_span)), .into_value(lhs_span)),
_ => Err(ShellError::OperatorMismatch { _ => Err(ShellError::OperatorMismatch {
op_span, op_span,
lhs_ty: Type::Custom, lhs_ty: Type::Custom(left.typetag_name().into()),
lhs_span, lhs_span,
rhs_ty: Type::Custom, rhs_ty: Type::Custom(right.typetag_name().into()),
rhs_span, rhs_span,
}), }),
} }

View File

@ -829,7 +829,7 @@ pub fn create_scope(
}) })
} }
for (command_name, decl_id) in commands_map { for ((command_name, _), decl_id) in commands_map {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(decl_id) {
let mut cols = vec![]; let mut cols = vec![];
let mut vals = vec![]; let mut vals = vec![];

View File

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

View File

@ -739,7 +739,10 @@ pub fn parse_internal_call(
call.decl_id = decl_id; call.decl_id = decl_id;
call.head = command_span; 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 { if signature.creates_scope {
working_set.enter_scope(); working_set.enter_scope();
@ -1009,7 +1012,8 @@ pub fn parse_call(
pos += 1; 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() { while maybe_decl_id.is_none() {
// Find the longest command match // Find the longest command match
@ -1031,7 +1035,7 @@ pub fn parse_call(
name.extend(name_part); 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 { 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 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 { if let Some(decl_id) = decl_id {
return (SyntaxShape::Custom(Box::new(shape), decl_id), err); 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 !shorthand.is_empty() {
if let Some(decl_id) = with_env { 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"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist), b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
b"export" => { 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( let (call, _) = parse_internal_call(
working_set, working_set,
lite_command.parts[0], lite_command.parts[0],
@ -4818,8 +4822,9 @@ pub fn parse_block(
); );
if idx == 0 { if idx == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let") { 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") { if let Some(let_env_decl_id) = working_set.find_decl(b"let-env", &Type::Any)
{
for expr in pipeline.expressions.iter_mut() { for expr in pipeline.expressions.iter_mut() {
if let Expression { if let Expression {
expr: Expr::Call(call), 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 { fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
let span = expr.span; 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 mut output = vec![];
let var_id = working_set.next_var_id(); let var_id = working_set.next_var_id();

View File

@ -662,3 +662,291 @@ mod range {
assert!(err.is_some()); 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"),
}
}
}

View File

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::{ast::Call, BlockId, Example, PipelineData, ShellError, Signature}; use crate::{ast::Call, BlockId, Example, PipelineData, ShellError, Signature, Type};
use super::{EngineState, Stack}; use super::{EngineState, Stack};
@ -66,6 +66,20 @@ pub trait Command: Send + Sync + CommandClone {
fn search_terms(&self) -> Vec<&str> { fn search_terms(&self) -> Vec<&str> {
vec![] vec![]
} }
// Command input type. The Type is used during parsing to find the
// correct internal command with similar names. The input type is
// obtained from the previous expression found in the pipeline
fn input_type(&self) -> Type {
Type::Any
}
// Command output type. The output type is the value from the command
// It is used during parsing to find the next command in case there
// are commands with similar names
fn output_type(&self) -> Type {
Type::Any
}
} }
pub trait CommandClone { pub trait CommandClone {

View File

@ -464,9 +464,9 @@ impl EngineState {
for overlay_frame in self.active_overlays(removed_overlays).iter().rev() { for overlay_frame in self.active_overlays(removed_overlays).iter().rev() {
visibility.append(&overlay_frame.visibility); visibility.append(&overlay_frame.visibility);
if let Some(decl_id) = overlay_frame.decls.get(name) { if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(&decl_id) {
return Some(*decl_id); return Some(decl_id);
} }
} }
} }
@ -533,9 +533,9 @@ impl EngineState {
for overlay_frame in self.active_overlays(&[]).iter().rev() { for overlay_frame in self.active_overlays(&[]).iter().rev() {
for decl in &overlay_frame.decls { for decl in &overlay_frame.decls {
if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(&decl.0 .0) {
let command = self.get_decl(*decl.1); let command = self.get_decl(*decl.1);
output.push((decl.0.clone(), Some(command.usage().to_string()))); output.push((decl.0 .0.clone(), Some(command.usage().to_string())));
} }
} }
} }
@ -614,7 +614,8 @@ impl EngineState {
decls_map.extend(new_decls); decls_map.extend(new_decls);
} }
let mut decls: Vec<(Vec<u8>, DeclId)> = decls_map.into_iter().collect(); let mut decls: Vec<(Vec<u8>, DeclId)> =
decls_map.into_iter().map(|(v, k)| (v.0, k)).collect();
decls.sort_by(|a, b| a.0.cmp(&b.0)); decls.sort_by(|a, b| a.0.cmp(&b.0));
decls.into_iter().map(|(_, id)| id) decls.into_iter().map(|(_, id)| id)
@ -743,6 +744,9 @@ pub struct StateWorkingSet<'a> {
pub permanent_state: &'a EngineState, pub permanent_state: &'a EngineState,
pub delta: StateDelta, pub delta: StateDelta,
pub external_commands: Vec<Vec<u8>>, pub external_commands: Vec<Vec<u8>>,
// Internal commands output that the next expression in the pipe will use to select a declaration
// that matches the name in the found output
pub found_outputs: Vec<Type>,
} }
/// A delta (or change set) between the current global state and a possible future global state. Deltas /// A delta (or change set) between the current global state and a possible future global state. Deltas
@ -867,6 +871,7 @@ impl<'a> StateWorkingSet<'a> {
delta: StateDelta::new(permanent_state), delta: StateDelta::new(permanent_state),
permanent_state, permanent_state,
external_commands: vec![], external_commands: vec![],
found_outputs: vec![],
} }
} }
@ -918,11 +923,13 @@ impl<'a> StateWorkingSet<'a> {
pub fn add_decl(&mut self, decl: Box<dyn Command>) -> DeclId { pub fn add_decl(&mut self, decl: Box<dyn Command>) -> DeclId {
let name = decl.name().as_bytes().to_vec(); let name = decl.name().as_bytes().to_vec();
let input_type = decl.input_type();
self.delta.decls.push(decl); self.delta.decls.push(decl);
let decl_id = self.num_decls() - 1; let decl_id = self.num_decls() - 1;
self.last_overlay_mut().decls.insert(name, decl_id); self.last_overlay_mut()
.insert_decl(name, input_type, decl_id);
decl_id decl_id
} }
@ -931,7 +938,7 @@ impl<'a> StateWorkingSet<'a> {
let overlay_frame = self.last_overlay_mut(); let overlay_frame = self.last_overlay_mut();
for (name, decl_id) in decls { for (name, decl_id) in decls {
overlay_frame.decls.insert(name, decl_id); overlay_frame.insert_decl(name, Type::Any, decl_id);
overlay_frame.visibility.use_decl_id(&decl_id); overlay_frame.visibility.use_decl_id(&decl_id);
} }
} }
@ -968,7 +975,7 @@ impl<'a> StateWorkingSet<'a> {
let overlay_frame = self.last_overlay_mut(); let overlay_frame = self.last_overlay_mut();
if let Some(decl_id) = overlay_frame.predecls.remove(name) { if let Some(decl_id) = overlay_frame.predecls.remove(name) {
overlay_frame.decls.insert(name.into(), decl_id); overlay_frame.insert_decl(name.into(), Type::Any, decl_id);
return Some(decl_id); return Some(decl_id);
} }
@ -998,11 +1005,11 @@ impl<'a> StateWorkingSet<'a> {
visibility.append(&overlay_frame.visibility); visibility.append(&overlay_frame.visibility);
if let Some(decl_id) = overlay_frame.decls.get(name) { if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(&decl_id) {
// Hide decl only if it's not already hidden // Hide decl only if it's not already hidden
overlay_frame.visibility.hide_decl_id(decl_id); overlay_frame.visibility.hide_decl_id(&decl_id);
return Some(*decl_id); return Some(decl_id);
} }
} }
} }
@ -1018,11 +1025,11 @@ impl<'a> StateWorkingSet<'a> {
{ {
visibility.append(&overlay_frame.visibility); visibility.append(&overlay_frame.visibility);
if let Some(decl_id) = overlay_frame.decls.get(name) { if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(&decl_id) {
// Hide decl only if it's not already hidden // Hide decl only if it's not already hidden
self.last_overlay_mut().visibility.hide_decl_id(decl_id); self.last_overlay_mut().visibility.hide_decl_id(&decl_id);
return Some(*decl_id); return Some(decl_id);
} }
} }
} }
@ -1254,7 +1261,7 @@ impl<'a> StateWorkingSet<'a> {
None None
} }
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> { pub fn find_decl(&self, name: &[u8], input: &Type) -> Option<DeclId> {
let mut removed_overlays = vec![]; let mut removed_overlays = vec![];
let mut visibility: Visibility = Visibility::new(); let mut visibility: Visibility = Visibility::new();
@ -1280,9 +1287,9 @@ impl<'a> StateWorkingSet<'a> {
} }
} }
if let Some(decl_id) = overlay_frame.decls.get(name) { if let Some(decl_id) = overlay_frame.get_decl(name, input) {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(&decl_id) {
return Some(*decl_id); return Some(decl_id);
} }
} }
} }
@ -1297,9 +1304,9 @@ impl<'a> StateWorkingSet<'a> {
{ {
visibility.append(&overlay_frame.visibility); visibility.append(&overlay_frame.visibility);
if let Some(decl_id) = overlay_frame.decls.get(name) { if let Some(decl_id) = overlay_frame.get_decl(name, input) {
if visibility.is_decl_id_visible(decl_id) { if visibility.is_decl_id_visible(&decl_id) {
return Some(*decl_id); return Some(decl_id);
} }
} }
} }
@ -1384,7 +1391,7 @@ impl<'a> StateWorkingSet<'a> {
.rev() .rev()
{ {
for decl in &overlay_frame.decls { for decl in &overlay_frame.decls {
if decl.0.starts_with(name) { if decl.0 .0.starts_with(name) {
return true; return true;
} }
} }
@ -1398,7 +1405,7 @@ impl<'a> StateWorkingSet<'a> {
.rev() .rev()
{ {
for decl in &overlay_frame.decls { for decl in &overlay_frame.decls {
if decl.0.starts_with(name) { if decl.0 .0.starts_with(name) {
return true; return true;
} }
} }
@ -1562,9 +1569,10 @@ impl<'a> StateWorkingSet<'a> {
let overlay_frame = scope_frame.get_overlay(*overlay_id); let overlay_frame = scope_frame.get_overlay(*overlay_id);
for decl in &overlay_frame.decls { for decl in &overlay_frame.decls {
if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(&decl.0 .0)
{
let command = self.get_decl(*decl.1); let command = self.get_decl(*decl.1);
output.push((decl.0.clone(), Some(command.usage().to_string()))); output.push((decl.0 .0.clone(), Some(command.usage().to_string())));
} }
} }
} }
@ -1718,8 +1726,8 @@ impl<'a> StateWorkingSet<'a> {
if let Some(overlay_id) = self.permanent_state.find_overlay(name) { if let Some(overlay_id) = self.permanent_state.find_overlay(name) {
let overlay_frame = self.permanent_state.get_overlay(overlay_id); let overlay_frame = self.permanent_state.get_overlay(overlay_id);
for (decl_name, decl_id) in &overlay_frame.decls { for (decl_key, decl_id) in &overlay_frame.decls {
result.insert(decl_name.to_owned(), *decl_id); result.insert(decl_key.0.to_owned(), *decl_id);
} }
} }
@ -1727,8 +1735,8 @@ impl<'a> StateWorkingSet<'a> {
if let Some(overlay_id) = scope_frame.find_overlay(name) { if let Some(overlay_id) = scope_frame.find_overlay(name) {
let overlay_frame = scope_frame.get_overlay(overlay_id); let overlay_frame = scope_frame.get_overlay(overlay_id);
for (decl_name, decl_id) in &overlay_frame.decls { for (decl_key, decl_id) in &overlay_frame.decls {
result.insert(decl_name.to_owned(), *decl_id); result.insert(decl_key.0.to_owned(), *decl_id);
} }
} }
} }

View File

@ -1,6 +1,7 @@
use crate::{AliasId, DeclId, ModuleId, OverlayId, Type, VarId};
use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use crate::{AliasId, DeclId, ModuleId, OverlayId, VarId};
pub static DEFAULT_OVERLAY_NAME: &str = "zero"; pub static DEFAULT_OVERLAY_NAME: &str = "zero";
@ -199,7 +200,7 @@ impl ScopeFrame {
pub struct OverlayFrame { pub struct OverlayFrame {
pub vars: HashMap<Vec<u8>, VarId>, pub vars: HashMap<Vec<u8>, VarId>,
pub predecls: HashMap<Vec<u8>, DeclId>, // temporary storage for predeclarations pub predecls: HashMap<Vec<u8>, DeclId>, // temporary storage for predeclarations
pub decls: HashMap<Vec<u8>, DeclId>, pub decls: HashMap<(Vec<u8>, Type), DeclId>,
pub aliases: HashMap<Vec<u8>, AliasId>, pub aliases: HashMap<Vec<u8>, AliasId>,
pub modules: HashMap<Vec<u8>, ModuleId>, pub modules: HashMap<Vec<u8>, ModuleId>,
pub visibility: Visibility, pub visibility: Visibility,
@ -218,4 +219,58 @@ impl OverlayFrame {
origin, origin,
} }
} }
pub fn insert_decl(&mut self, name: Vec<u8>, input: Type, decl_id: DeclId) -> Option<DeclId> {
self.decls.insert((name, input), decl_id)
}
pub fn get_decl(&self, name: &[u8], input: &Type) -> Option<DeclId> {
self.decls.get(&(name, input) as &dyn DeclKey).cloned()
}
}
trait DeclKey {
fn name(&self) -> &[u8];
fn input(&self) -> &Type;
}
impl Hash for dyn DeclKey + '_ {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().hash(state);
self.input().hash(state);
}
}
impl PartialEq for dyn DeclKey + '_ {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name() && self.input() == other.input()
}
}
impl Eq for dyn DeclKey + '_ {}
impl<'a> DeclKey for (&'a [u8], &Type) {
fn name(&self) -> &[u8] {
self.0
}
fn input(&self) -> &Type {
self.1
}
}
impl DeclKey for (Vec<u8>, Type) {
fn name(&self) -> &[u8] {
&self.0
}
fn input(&self) -> &Type {
&self.1
}
}
impl<'a> Borrow<dyn DeclKey + 'a> for (Vec<u8>, Type) {
fn borrow(&self) -> &(dyn DeclKey + 'a) {
self
}
} }

View File

@ -4,7 +4,7 @@ use std::fmt::Display;
use crate::SyntaxShape; use crate::SyntaxShape;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum Type { pub enum Type {
Int, Int,
Float, Float,
@ -25,7 +25,7 @@ pub enum Type {
Any, Any,
Error, Error,
Binary, Binary,
Custom, Custom(String),
Signature, Signature,
} }
@ -51,7 +51,7 @@ impl Type {
Type::Any => SyntaxShape::Any, Type::Any => SyntaxShape::Any,
Type::Error => SyntaxShape::Any, Type::Error => SyntaxShape::Any,
Type::Binary => SyntaxShape::Binary, Type::Binary => SyntaxShape::Binary,
Type::Custom => SyntaxShape::Any, Type::Custom(_) => SyntaxShape::Any,
Type::Signature => SyntaxShape::Signature, Type::Signature => SyntaxShape::Signature,
} }
} }
@ -95,7 +95,7 @@ impl Display for Type {
Type::Any => write!(f, "any"), Type::Any => write!(f, "any"),
Type::Error => write!(f, "error"), Type::Error => write!(f, "error"),
Type::Binary => write!(f, "binary"), Type::Binary => write!(f, "binary"),
Type::Custom => write!(f, "custom"), Type::Custom(custom) => write!(f, "custom<{}>", custom),
Type::Signature => write!(f, "signature"), Type::Signature => write!(f, "signature"),
} }
} }

View File

@ -417,7 +417,7 @@ impl Value {
Value::Error { .. } => Type::Error, Value::Error { .. } => Type::Error,
Value::Binary { .. } => Type::Binary, Value::Binary { .. } => Type::Binary,
Value::CellPath { .. } => Type::CellPath, Value::CellPath { .. } => Type::CellPath,
Value::CustomValue { .. } => Type::Custom, Value::CustomValue { val, .. } => Type::Custom(val.typetag_name().into()),
} }
} }