forked from extern/nushell
Produce ArgumentError for signature mismatch
ArgumentError also automatically produces diagnostics
This commit is contained in:
parent
5cac3d1135
commit
711ed05b43
@ -2,6 +2,7 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::parser::{Span, Spanned};
|
use crate::parser::{Span, Spanned};
|
||||||
|
use ansi_term::Color;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use language_reporting::{Diagnostic, Label, Severity};
|
use language_reporting::{Diagnostic, Label, Severity};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@ -33,6 +34,13 @@ impl Description {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
|
pub enum ArgumentError {
|
||||||
|
MissingMandatoryFlag(String),
|
||||||
|
MissingMandatoryPositional(String),
|
||||||
|
MissingValueForName(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||||
pub enum ShellError {
|
pub enum ShellError {
|
||||||
String(StringError),
|
String(StringError),
|
||||||
@ -44,6 +52,10 @@ pub enum ShellError {
|
|||||||
subpath: Description,
|
subpath: Description,
|
||||||
expr: Description,
|
expr: Description,
|
||||||
},
|
},
|
||||||
|
ArgumentError {
|
||||||
|
error: ArgumentError,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
Diagnostic(ShellDiagnostic),
|
Diagnostic(ShellDiagnostic),
|
||||||
CoerceError {
|
CoerceError {
|
||||||
left: Spanned<String>,
|
left: Spanned<String>,
|
||||||
@ -107,6 +119,32 @@ impl ShellError {
|
|||||||
ShellError::String(StringError { title, .. }) => {
|
ShellError::String(StringError { title, .. }) => {
|
||||||
Diagnostic::new(Severity::Error, title)
|
Diagnostic::new(Severity::Error, title)
|
||||||
}
|
}
|
||||||
|
ShellError::ArgumentError { error, span } => match error {
|
||||||
|
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Command requires {}{}",
|
||||||
|
Color::Cyan.paint("--"),
|
||||||
|
Color::Cyan.paint(name)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_label(Label::new_primary(span)),
|
||||||
|
ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!("Command requires {}", Color::Cyan.paint(name)),
|
||||||
|
)
|
||||||
|
.with_label(Label::new_primary(span)),
|
||||||
|
|
||||||
|
ArgumentError::MissingValueForName(name) => Diagnostic::new(
|
||||||
|
Severity::Error,
|
||||||
|
format!(
|
||||||
|
"Missing value for flag {}{}",
|
||||||
|
Color::Cyan.paint("--"),
|
||||||
|
Color::Cyan.paint(name)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_label(Label::new_primary(span)),
|
||||||
|
},
|
||||||
ShellError::TypeError {
|
ShellError::TypeError {
|
||||||
expected,
|
expected,
|
||||||
actual:
|
actual:
|
||||||
@ -271,6 +309,7 @@ impl std::fmt::Display for ShellError {
|
|||||||
ShellError::String(s) => write!(f, "{}", &s.title),
|
ShellError::String(s) => write!(f, "{}", &s.title),
|
||||||
ShellError::TypeError { .. } => write!(f, "TypeError"),
|
ShellError::TypeError { .. } => write!(f, "TypeError"),
|
||||||
ShellError::MissingProperty { .. } => write!(f, "MissingProperty"),
|
ShellError::MissingProperty { .. } => write!(f, "MissingProperty"),
|
||||||
|
ShellError::ArgumentError { .. } => write!(f, "ArgumentError"),
|
||||||
ShellError::Diagnostic(_) => write!(f, "<diagnostic>"),
|
ShellError::Diagnostic(_) => write!(f, "<diagnostic>"),
|
||||||
ShellError::CoerceError { .. } => write!(f, "CoerceError"),
|
ShellError::CoerceError { .. } => write!(f, "CoerceError"),
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,19 @@ pub enum RawExpression {
|
|||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawExpression {
|
||||||
|
pub fn type_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
RawExpression::Literal(literal) => literal.type_name(),
|
||||||
|
RawExpression::Variable(..) => "variable",
|
||||||
|
RawExpression::Binary(..) => "binary",
|
||||||
|
RawExpression::Block(..) => "block",
|
||||||
|
RawExpression::Path(..) => "path",
|
||||||
|
RawExpression::Boolean(..) => "boolean",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Expression = Spanned<RawExpression>;
|
pub type Expression = Spanned<RawExpression>;
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
@ -99,6 +112,17 @@ pub enum Literal {
|
|||||||
Bare,
|
Bare,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Literal {
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Literal::Integer(_) => "integer",
|
||||||
|
Literal::Size(..) => "size",
|
||||||
|
Literal::String(..) => "string",
|
||||||
|
Literal::Bare => "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum Variable {
|
pub enum Variable {
|
||||||
It(Span),
|
It(Span),
|
||||||
|
@ -58,8 +58,10 @@ pub fn baseline_parse_next_expr(
|
|||||||
|
|
||||||
let second = match tokens.next() {
|
let second = match tokens.next() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ShellError::unimplemented(
|
return Err(ShellError::maybe_labeled_error(
|
||||||
"Expected op followed by another expr, found nothing",
|
"Expected something after an operator",
|
||||||
|
"operator",
|
||||||
|
Some(op.span),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Some(token) => baseline_parse_semantic_token(token, registry, source)?,
|
Some(token) => baseline_parse_semantic_token(token, registry, source)?,
|
||||||
@ -124,9 +126,11 @@ pub fn baseline_parse_next_expr(
|
|||||||
item: hir::RawExpression::Variable(..),
|
item: hir::RawExpression::Variable(..),
|
||||||
..
|
..
|
||||||
} => first,
|
} => first,
|
||||||
_ => {
|
Spanned { span, item } => {
|
||||||
return Err(ShellError::unimplemented(
|
return Err(ShellError::labeled_error(
|
||||||
"The first part of a block must be a string",
|
"The first part of an un-braced block must be a column name",
|
||||||
|
item.type_name(),
|
||||||
|
span,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::errors::ShellError;
|
use crate::errors::{ArgumentError, ShellError};
|
||||||
use crate::parser::registry::{CommandConfig, CommandRegistry, NamedType};
|
use crate::parser::registry::{CommandConfig, CommandRegistry, NamedType};
|
||||||
use crate::parser::{baseline_parse_tokens, CallNode, Spanned};
|
use crate::parser::{baseline_parse_tokens, CallNode, Span, Spanned};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
hir::{self, NamedArguments},
|
hir::{self, NamedArguments},
|
||||||
Flag, RawToken, TokenNode,
|
Flag, RawToken, TokenNode,
|
||||||
@ -14,13 +14,13 @@ pub fn parse_command(
|
|||||||
call: &Spanned<CallNode>,
|
call: &Spanned<CallNode>,
|
||||||
source: &Text,
|
source: &Text,
|
||||||
) -> Result<hir::Call, ShellError> {
|
) -> Result<hir::Call, ShellError> {
|
||||||
let Spanned { item: call, .. } = call;
|
let Spanned { item: raw_call, .. } = call;
|
||||||
|
|
||||||
trace!("Processing {:?}", config);
|
trace!("Processing {:?}", config);
|
||||||
|
|
||||||
let head = parse_command_head(call.head())?;
|
let head = parse_command_head(call.head())?;
|
||||||
|
|
||||||
let children: Option<Vec<TokenNode>> = call.children().as_ref().map(|nodes| {
|
let children: Option<Vec<TokenNode>> = raw_call.children().as_ref().map(|nodes| {
|
||||||
nodes
|
nodes
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -31,7 +31,7 @@ pub fn parse_command(
|
|||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
match parse_command_tail(&config, registry, children, source)? {
|
match parse_command_tail(&config, registry, children, source, call.span)? {
|
||||||
None => Ok(hir::Call::new(Box::new(head), None, None)),
|
None => Ok(hir::Call::new(Box::new(head), None, None)),
|
||||||
Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)),
|
Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)),
|
||||||
}
|
}
|
||||||
@ -66,9 +66,10 @@ fn parse_command_tail(
|
|||||||
registry: &dyn CommandRegistry,
|
registry: &dyn CommandRegistry,
|
||||||
tail: Option<Vec<TokenNode>>,
|
tail: Option<Vec<TokenNode>>,
|
||||||
source: &Text,
|
source: &Text,
|
||||||
|
command_span: Span,
|
||||||
) -> Result<Option<(Option<Vec<hir::Expression>>, Option<NamedArguments>)>, ShellError> {
|
) -> Result<Option<(Option<Vec<hir::Expression>>, Option<NamedArguments>)>, ShellError> {
|
||||||
let tail = &mut match &tail {
|
let tail = &mut match &tail {
|
||||||
None => return Ok(None),
|
None => hir::TokensIterator::new(&[]),
|
||||||
Some(tail) => hir::TokensIterator::new(tail),
|
Some(tail) => hir::TokensIterator::new(tail),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,10 +86,19 @@ fn parse_command_tail(
|
|||||||
|
|
||||||
named.insert_switch(name, flag);
|
named.insert_switch(name, flag);
|
||||||
}
|
}
|
||||||
NamedType::Mandatory(kind) => match extract_mandatory(name, tail, source) {
|
NamedType::Mandatory(kind) => match extract_mandatory(name, tail, source, command_span)
|
||||||
|
{
|
||||||
Err(err) => return Err(err), // produce a correct diagnostic
|
Err(err) => return Err(err), // produce a correct diagnostic
|
||||||
Ok((pos, _flag)) => {
|
Ok((pos, flag)) => {
|
||||||
tail.move_to(pos);
|
tail.move_to(pos);
|
||||||
|
|
||||||
|
if tail.at_end() {
|
||||||
|
return Err(ShellError::ArgumentError {
|
||||||
|
error: ArgumentError::MissingValueForName(name.to_string()),
|
||||||
|
span: flag.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let expr = hir::baseline_parse_next_expr(
|
let expr = hir::baseline_parse_next_expr(
|
||||||
tail,
|
tail,
|
||||||
registry,
|
registry,
|
||||||
@ -102,8 +112,16 @@ fn parse_command_tail(
|
|||||||
},
|
},
|
||||||
NamedType::Optional(kind) => match extract_optional(name, tail, source) {
|
NamedType::Optional(kind) => match extract_optional(name, tail, source) {
|
||||||
Err(err) => return Err(err), // produce a correct diagnostic
|
Err(err) => return Err(err), // produce a correct diagnostic
|
||||||
Ok(Some((pos, _flag))) => {
|
Ok(Some((pos, flag))) => {
|
||||||
tail.move_to(pos);
|
tail.move_to(pos);
|
||||||
|
|
||||||
|
if tail.at_end() {
|
||||||
|
return Err(ShellError::ArgumentError {
|
||||||
|
error: ArgumentError::MissingValueForName(name.to_string()),
|
||||||
|
span: flag.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let expr = hir::baseline_parse_next_expr(
|
let expr = hir::baseline_parse_next_expr(
|
||||||
tail,
|
tail,
|
||||||
registry,
|
registry,
|
||||||
@ -132,7 +150,10 @@ fn parse_command_tail(
|
|||||||
trace!("Processing mandatory {:?}", arg);
|
trace!("Processing mandatory {:?}", arg);
|
||||||
|
|
||||||
if tail.len() == 0 {
|
if tail.len() == 0 {
|
||||||
return Err(ShellError::unimplemented("Missing mandatory argument"));
|
return Err(ShellError::ArgumentError {
|
||||||
|
error: ArgumentError::MissingMandatoryPositional(arg.name().to_string()),
|
||||||
|
span: command_span,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = hir::baseline_parse_next_expr(tail, registry, source, arg.to_coerce_hint())?;
|
let result = hir::baseline_parse_next_expr(tail, registry, source, arg.to_coerce_hint())?;
|
||||||
@ -189,23 +210,19 @@ fn extract_mandatory(
|
|||||||
name: &str,
|
name: &str,
|
||||||
tokens: &mut hir::TokensIterator<'a>,
|
tokens: &mut hir::TokensIterator<'a>,
|
||||||
source: &Text,
|
source: &Text,
|
||||||
) -> Result<(usize, Flag), ShellError> {
|
span: Span,
|
||||||
|
) -> Result<(usize, Spanned<Flag>), ShellError> {
|
||||||
let flag = tokens.extract(|t| t.as_flag(name, source));
|
let flag = tokens.extract(|t| t.as_flag(name, source));
|
||||||
|
|
||||||
match flag {
|
match flag {
|
||||||
None => Err(ShellError::unimplemented(
|
None => Err(ShellError::ArgumentError {
|
||||||
"Better error: mandatory flags must be present",
|
error: ArgumentError::MissingMandatoryFlag(name.to_string()),
|
||||||
)),
|
span,
|
||||||
|
}),
|
||||||
|
|
||||||
Some((pos, flag)) => {
|
Some((pos, flag)) => {
|
||||||
if tokens.len() <= pos {
|
|
||||||
return Err(ShellError::unimplemented(
|
|
||||||
"Better errors: mandatory flags must be followed by values",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.remove(pos);
|
tokens.remove(pos);
|
||||||
|
Ok((pos, flag))
|
||||||
Ok((pos, *flag))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,21 +231,14 @@ fn extract_optional(
|
|||||||
name: &str,
|
name: &str,
|
||||||
tokens: &mut hir::TokensIterator<'a>,
|
tokens: &mut hir::TokensIterator<'a>,
|
||||||
source: &Text,
|
source: &Text,
|
||||||
) -> Result<(Option<(usize, Flag)>), ShellError> {
|
) -> Result<(Option<(usize, Spanned<Flag>)>), ShellError> {
|
||||||
let flag = tokens.extract(|t| t.as_flag(name, source));
|
let flag = tokens.extract(|t| t.as_flag(name, source));
|
||||||
|
|
||||||
match flag {
|
match flag {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some((pos, flag)) => {
|
Some((pos, flag)) => {
|
||||||
if tokens.len() <= pos {
|
|
||||||
return Err(ShellError::unimplemented(
|
|
||||||
"Better errors: optional flags must be followed by values",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.remove(pos);
|
tokens.remove(pos);
|
||||||
|
Ok(Some((pos, flag)))
|
||||||
Ok(Some((pos, *flag)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,13 @@ impl PositionalType {
|
|||||||
PositionalType::Block(_) => Some(ExpressionKindHint::Block),
|
PositionalType::Block(_) => Some(ExpressionKindHint::Block),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
PositionalType::Value(s) => s,
|
||||||
|
PositionalType::Block(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Getters)]
|
#[derive(Debug, Getters)]
|
||||||
|
Loading…
Reference in New Issue
Block a user