use code_block::{CodeBlock, CodeOrigin};
use json::{self, JsonValue};
use regex::Regex;
use std;
use std::fmt;
use std::io;
#[derive(Debug, Clone)]
pub struct CompilationError {
message: String,
pub json: JsonValue,
pub(crate) code_origins: Vec<CodeOrigin>,
spanned_messages: Vec<SpannedMessage>,
}
fn spans_in_local_source(span: &JsonValue) -> Option<&JsonValue> {
if let Some(file_name) = span["file_name"].as_str() {
if file_name.ends_with("lib.rs") {
return Some(span);
}
}
let expansion = &span["expansion"];
if expansion.is_object() {
return spans_in_local_source(&expansion["span"]);
}
None
}
fn get_code_origins_for_span(span: &JsonValue, code_block: &CodeBlock) -> Vec<CodeOrigin> {
let mut code_origins = Vec::new();
if let Some(span) = spans_in_local_source(span) {
if let (Some(line_start), Some(line_end)) =
(span["line_start"].as_usize(), span["line_end"].as_usize())
{
for line in line_start..=line_end {
code_origins.push(code_block.origin_for_line(line));
}
}
}
code_origins
}
fn get_code_origins(json: &JsonValue, code_block: &CodeBlock) -> Vec<CodeOrigin> {
let mut code_origins = Vec::new();
if let JsonValue::Array(spans) = &json["spans"] {
for span in spans {
code_origins.extend(get_code_origins_for_span(span, code_block));
}
}
code_origins
}
impl CompilationError {
pub(crate) fn opt_new(mut json: JsonValue, code_block: &CodeBlock) -> Option<CompilationError> {
let mut code_origins = get_code_origins(&json, code_block);
let mut user_error_json = None;
if let JsonValue::Array(children) = &json["children"] {
for child in children {
let child_origins = get_code_origins(child, code_block);
if !code_origins.contains(&CodeOrigin::UserSupplied)
&& child_origins.contains(&CodeOrigin::UserSupplied)
{
user_error_json = Some(child.clone());
code_origins = child_origins;
break;
} else {
code_origins.extend(child_origins);
}
}
}
if let Some(user_error_json) = user_error_json {
json = user_error_json;
}
let message = if let Some(message) = json["message"].as_str() {
if message.starts_with("aborting due to")
|| message.starts_with("For more information about")
|| message.starts_with("Some errors occurred")
{
return None;
}
message.to_owned()
} else {
return None;
};
Some(CompilationError {
spanned_messages: build_spanned_messages(&json, code_block),
message,
json,
code_origins,
})
}
pub fn is_from_user_code(&self) -> bool {
self.code_origins.contains(&CodeOrigin::UserSupplied)
}
pub fn is_from_generated_code(&self) -> bool {
self.code_origins.contains(&CodeOrigin::OtherGeneratedCode)
}
pub fn message(&self) -> String {
self.message.clone()
}
pub fn code(&self) -> Option<&str> {
if let JsonValue::Object(code) = &self.json["code"] {
return code["code"].as_str();
}
None
}
pub fn explanation(&self) -> Option<&str> {
if let JsonValue::Object(code) = &self.json["code"] {
return code["explanation"].as_str();
}
None
}
pub fn evcxr_extra_hint(&self) -> Option<&'static str> {
if let Some(code) = self.code() {
Some(match code {
"E0597" => {
"Values assigned to variables in Evcxr cannot contain references \
(unless they're static)"
}
_ => return None,
})
} else {
None
}
}
pub fn spanned_messages(&self) -> &[SpannedMessage] {
&self.spanned_messages[..]
}
pub fn help(&self) -> Vec<String> {
if let JsonValue::Array(children) = &self.json["children"] {
children
.iter()
.filter_map(|child| {
if child["level"].as_str() != Some("help") {
return None;
}
child["message"].as_str().map(|s| s.to_owned())
})
.collect()
} else {
vec![]
}
}
pub fn rendered(&self) -> String {
self.json["rendered"]
.as_str()
.unwrap_or_else(|| "")
.to_owned()
}
pub(crate) fn get_actual_type(&self) -> Option<String> {
lazy_static! {
static ref TYPE_ERROR_RE: Regex =
Regex::new("expected type `(.*)`\n *found type `(.*)`").unwrap();
}
if let JsonValue::Array(children) = &self.json["children"] {
for child in children {
if let Some(message) = child["message"].as_str() {
if let Some(captures) = TYPE_ERROR_RE.captures(message) {
return Some(captures[2].to_owned());
}
}
}
}
None
}
}
fn build_spanned_messages(json: &JsonValue, code_block: &CodeBlock) -> Vec<SpannedMessage> {
let mut output_spans = Vec::new();
if let JsonValue::Array(spans) = &json["spans"] {
let all_lines = code_block.get_lines();
for span_json in spans {
output_spans.push(SpannedMessage::from_json(span_json, &all_lines, code_block));
}
}
if output_spans.iter().any(|s| s.span.is_some()) {
output_spans.retain(|s| s.span.is_some());
}
output_spans
}
#[derive(Debug, Clone, Copy)]
pub struct Span {
pub start_column: usize,
pub end_column: usize,
}
#[derive(Debug, Clone)]
pub struct SpannedMessage {
pub span: Option<Span>,
pub lines: Vec<String>,
pub label: String,
}
impl SpannedMessage {
fn from_json(
span_json: &JsonValue,
all_lines: &[String],
code_block: &CodeBlock,
) -> SpannedMessage {
let mut lines = Vec::new();
let span = if let (
Some(file_name),
Some(start_column),
Some(end_column),
Some(start_line),
Some(end_line),
) = (
span_json["file_name"].as_str(),
span_json["column_start"].as_usize(),
span_json["column_end"].as_usize(),
span_json["line_start"].as_usize(),
span_json["line_end"].as_usize(),
) {
if file_name.ends_with("lib.rs") {
if start_line >= 1 && end_line <= all_lines.len() {
lines.extend(all_lines[start_line - 1..end_line].iter().cloned());
}
if get_code_origins_for_span(span_json, code_block)
.iter()
.all(|o| *o == CodeOrigin::UserSupplied)
{
Some(Span {
start_column,
end_column,
})
} else {
None
}
} else {
None
}
} else {
None
};
if span.is_none() {
let expansion_span_json = &span_json["expansion"]["span"];
if !expansion_span_json.is_empty() {
let mut message =
SpannedMessage::from_json(expansion_span_json, all_lines, code_block);
if message.span.is_some() {
if let Some(label) = span_json["label"].as_str() {
message.label = label.to_owned();
}
return message;
}
}
}
SpannedMessage {
span,
lines,
label: span_json["label"]
.as_str()
.map(|s| s.to_owned())
.unwrap_or_else(String::new),
}
}
}
#[derive(Debug)]
pub enum Error {
CompilationErrors(Vec<CompilationError>),
JustMessage(String),
ChildProcessTerminated(String),
}
impl Error {
pub(crate) fn without_non_reportable_errors(mut self) -> Self {
if let Error::CompilationErrors(errors) = &mut self {
if errors.iter().any(|error| error.is_from_user_code()) {
errors.retain(|error| error.is_from_user_code())
}
}
self
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::CompilationErrors(errors) => {
for error in errors {
write!(f, "{}", error.message())?;
}
}
Error::JustMessage(message) | Error::ChildProcessTerminated(message) => {
write!(f, "{}", message)?
}
}
Ok(())
}
}
impl From<std::fmt::Error> for Error {
fn from(error: std::fmt::Error) -> Self {
Error::JustMessage(error.to_string())
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::JustMessage(error.to_string())
}
}
impl From<json::Error> for Error {
fn from(error: json::Error) -> Self {
Error::JustMessage(error.to_string())
}
}
impl<'a> From<&'a io::Error> for Error {
fn from(error: &'a io::Error) -> Self {
Error::JustMessage(error.to_string())
}
}
impl From<std::str::Utf8Error> for Error {
fn from(error: std::str::Utf8Error) -> Self {
Error::JustMessage(error.to_string())
}
}
impl From<String> for Error {
fn from(message: String) -> Self {
Error::JustMessage(message)
}
}
impl<'a> From<&'a str> for Error {
fn from(message: &str) -> Self {
Error::JustMessage(message.to_owned())
}
}
macro_rules! bail {
($e:expr) => {return Err($crate::Error::from($e))};
($fmt:expr, $($arg:tt)+) => {return Err($crate::Error::from(format!($fmt, $($arg)+)))}
}