From 828585a31206d64ca8be83d5160cc3bf743f9ef5 Mon Sep 17 00:00:00 2001
From: Jonathan Turner <jonathan.d.turner@gmail.com>
Date: Tue, 10 Aug 2021 17:55:25 +1200
Subject: [PATCH] add more type helpers and span fixes

---
 src/errors.rs       | 22 ++++++++++++-----
 src/eval.rs         | 59 ++++++++++++++++++++++++++++++++++++---------
 src/parser.rs       |  2 +-
 src/parser_state.rs | 25 ++++++++++++++++++-
 4 files changed, 89 insertions(+), 19 deletions(-)

diff --git a/src/errors.rs b/src/errors.rs
index 2af1c7a16b..2b0e213e5f 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -78,7 +78,7 @@ impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> {
                 if count > line_index {
                     break;
                 } else if count == line_index {
-                    start = Some(byte.0);
+                    start = Some(byte.0 + 1);
                 }
             }
         }
@@ -286,12 +286,15 @@ pub fn report_shell_error(
     let config = codespan_reporting::term::Config::default();
 
     let diagnostic = match error {
-        ShellError::Mismatch(missing, span) => {
-            let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
+        ShellError::OperatorMismatch(operator, ty1, span1, ty2, span2) => {
+            let (diag_file_id1, diag_range1) = convert_span_to_diag(working_set, span1)?;
+            let (diag_file_id2, diag_range2) = convert_span_to_diag(working_set, span2)?;
             Diagnostic::error()
-                .with_message("Type mismatch during operation")
-                .with_labels(vec![Label::primary(diag_file_id, diag_range)
-                    .with_message(format!("expected {}", missing))])
+                .with_message(format!("Type mismatch during operation '{}'", operator))
+                .with_labels(vec![
+                    Label::primary(diag_file_id1, diag_range1).with_message(ty1.to_string()),
+                    Label::secondary(diag_file_id2, diag_range2).with_message(ty2.to_string()),
+                ])
         }
         ShellError::Unsupported(span) => {
             let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
@@ -312,6 +315,13 @@ pub fn report_shell_error(
                     Label::primary(diag_file_id, diag_range).with_message("variable not found")
                 ])
         }
+        ShellError::CantConvert(s, span) => {
+            let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?;
+            Diagnostic::error()
+                .with_message(format!("Can't convert to {}", s))
+                .with_labels(vec![Label::primary(diag_file_id, diag_range)
+                    .with_message(format!("can't convert to {}", s))])
+        }
     };
 
     // println!("DIAG");
diff --git a/src/eval.rs b/src/eval.rs
index 3ad4820c69..b30c0e30e4 100644
--- a/src/eval.rs
+++ b/src/eval.rs
@@ -1,15 +1,17 @@
 use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant};
 
 use crate::{
-    parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId,
+    parser::Operator, parser_state::Type, Block, BlockId, Call, Expr, Expression, ParserState,
+    Span, Statement, VarId,
 };
 
 #[derive(Debug)]
 pub enum ShellError {
-    Mismatch(String, Span),
+    OperatorMismatch(String, Type, Span, Type, Span),
     Unsupported(Span),
     InternalError(String),
     VariableNotFound(Span),
+    CantConvert(String, Span),
 }
 
 #[derive(Debug, Clone)]
@@ -27,7 +29,7 @@ impl Value {
     pub fn as_string(&self) -> Result<String, ShellError> {
         match self {
             Value::String { val, .. } => Ok(val.to_string()),
-            _ => Err(ShellError::Mismatch("string".into(), self.span())),
+            _ => Err(ShellError::CantConvert("string".into(), self.span())),
         }
     }
 
@@ -42,6 +44,32 @@ impl Value {
             Value::Nothing { span, .. } => *span,
         }
     }
+
+    pub fn with_span(mut self, new_span: Span) -> Value {
+        match &mut self {
+            Value::Bool { span, .. } => *span = new_span,
+            Value::Int { span, .. } => *span = new_span,
+            Value::Float { span, .. } => *span = new_span,
+            Value::String { span, .. } => *span = new_span,
+            Value::List { span, .. } => *span = new_span,
+            Value::Block { span, .. } => *span = new_span,
+            Value::Nothing { span, .. } => *span = new_span,
+        }
+
+        self
+    }
+
+    pub fn get_type(&self) -> Type {
+        match self {
+            Value::Bool { .. } => Type::Bool,
+            Value::Int { .. } => Type::Int,
+            Value::Float { .. } => Type::Float,
+            Value::String { .. } => Type::String,
+            Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME
+            Value::Nothing { .. } => Type::Nothing,
+            Value::Block { .. } => Type::Block,
+        }
+    }
 }
 
 impl PartialEq for Value {
@@ -80,29 +108,37 @@ impl Display for Value {
 
 impl Value {
     pub fn add(&self, rhs: &Value) -> Result<Value, ShellError> {
+        let span = crate::parser::span(&[self.span(), rhs.span()]);
+
         match (self, rhs) {
             (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int {
                 val: lhs + rhs,
-                span: Span::unknown(),
+                span,
             }),
             (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float {
                 val: *lhs as f64 + *rhs,
-                span: Span::unknown(),
+                span,
             }),
             (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float {
                 val: *lhs + *rhs as f64,
-                span: Span::unknown(),
+                span,
             }),
             (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float {
                 val: lhs + rhs,
-                span: Span::unknown(),
+                span,
             }),
             (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String {
                 val: lhs.to_string() + rhs,
-                span: Span::unknown(),
+                span,
             }),
 
-            _ => Err(ShellError::Mismatch("addition".into(), self.span())),
+            _ => Err(ShellError::OperatorMismatch(
+                "+".into(),
+                self.get_type(),
+                self.span(),
+                rhs.get_type(),
+                rhs.span(),
+            )),
         }
     }
 }
@@ -193,7 +229,7 @@ pub fn eval_operator(
             expr: Expr::Operator(operator),
             ..
         } => Ok(operator.clone()),
-        Expression { span, .. } => Err(ShellError::Mismatch("operator".to_string(), *span)),
+        Expression { span, .. } => Err(ShellError::Unsupported(*span)),
     }
 }
 
@@ -280,7 +316,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result<Value, ShellErr
                     Ok(Value::Nothing { span })
                 }
             }
-            _ => Err(ShellError::Mismatch("bool".into(), Span::unknown())),
+            _ => Err(ShellError::CantConvert("bool".into(), result.span())),
         }
     } else if decl.signature.name == "build-string" {
         let mut output = vec![];
@@ -385,6 +421,7 @@ pub fn eval_expression(
         }),
         Expr::Var(var_id) => stack
             .get_var(*var_id)
+            .map(|x| x.with_span(expr.span))
             .map_err(move |_| ShellError::VariableNotFound(expr.span)),
         Expr::Call(call) => eval_call(state, stack, call),
         Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)),
diff --git a/src/parser.rs b/src/parser.rs
index 91ce8fc8c6..ca27f2ce60 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -375,7 +375,7 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError>
     }
 }
 
-fn span(spans: &[Span]) -> Span {
+pub(crate) fn span(spans: &[Span]) -> Span {
     let length = spans.len();
 
     if length == 0 {
diff --git a/src/parser_state.rs b/src/parser_state.rs
index 50e12b8e92..6dafdb6336 100644
--- a/src/parser_state.rs
+++ b/src/parser_state.rs
@@ -1,6 +1,6 @@
 use crate::{parser::Block, Declaration, Span};
 use core::panic;
-use std::{collections::HashMap, slice::Iter};
+use std::{collections::HashMap, fmt::Display, slice::Iter};
 
 #[derive(Debug)]
 pub struct ParserState {
@@ -15,6 +15,7 @@ pub struct ParserState {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Type {
     Int,
+    Float,
     Bool,
     String,
     Block,
@@ -24,10 +25,32 @@ pub enum Type {
     Filesize,
     List(Box<Type>),
     Number,
+    Nothing,
     Table,
     Unknown,
 }
 
+impl Display for Type {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Type::Block => write!(f, "block"),
+            Type::Bool => write!(f, "bool"),
+            Type::ColumnPath => write!(f, "column path"),
+            Type::Duration => write!(f, "duration"),
+            Type::FilePath => write!(f, "filepath"),
+            Type::Filesize => write!(f, "filesize"),
+            Type::Float => write!(f, "float"),
+            Type::Int => write!(f, "int"),
+            Type::List(l) => write!(f, "list<{}>", l),
+            Type::Nothing => write!(f, "nothing"),
+            Type::Number => write!(f, "number"),
+            Type::String => write!(f, "string"),
+            Type::Table => write!(f, "table"),
+            Type::Unknown => write!(f, "unknown"),
+        }
+    }
+}
+
 pub type VarId = usize;
 pub type DeclId = usize;
 pub type BlockId = usize;