git.delta.rocks / jrsonnet / refs/commits / 796c7a04057c

difftreelog

fix explaining trace underflow

Yaroslav Bolyukin2022-11-03parent: #c607d6b.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/trace/mod.rs
1use std::path::{Path, PathBuf};23use jrsonnet_gcmodule::Trace;4use jrsonnet_parser::{CodeLocation, Source};56use crate::{error::Error, LocError, State};78/// The way paths should be displayed9#[derive(Clone, Trace)]10pub enum PathResolver {11	/// Only filename12	FileName,13	/// Absolute path14	Absolute,15	/// Path relative to base directory16	Relative(PathBuf),17}1819impl PathResolver {20	/// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure21	pub fn new_cwd_fallback() -> Self {22		std::env::current_dir().map_or(Self::Absolute, Self::Relative)23	}24	pub fn resolve(&self, from: &Path) -> String {25		match self {26			Self::FileName => from27				.file_name()28				.expect("file name exists")29				.to_string_lossy()30				.into_owned(),31			Self::Absolute => from.to_string_lossy().into_owned(),32			Self::Relative(base) => {33				if from.is_relative() {34					return from.to_string_lossy().into_owned();35				}36				pathdiff::diff_paths(from, base)37					.expect("base is absolute")38					.to_string_lossy()39					.into_owned()40			}41		}42	}43}4445/// Implements pretty-printing of traces46#[allow(clippy::module_name_repetitions)]47pub trait TraceFormat: Trace {48	fn write_trace(49		&self,50		out: &mut dyn std::fmt::Write,51		s: &State,52		error: &LocError,53	) -> Result<(), std::fmt::Error>;54}5556fn print_code_location(57	out: &mut impl std::fmt::Write,58	start: &CodeLocation,59	end: &CodeLocation,60) -> Result<(), std::fmt::Error> {61	if start.line == end.line {62		if start.column == end.column {63			write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;64		} else {65			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;66		}67	} else {68		write!(69			out,70			"{}:{}-{}:{}",71			start.line,72			end.column.saturating_sub(1),73			start.line,74			end.column75		)?;76	}77	Ok(())78}7980/// vanilla-like jsonnet formatting81#[derive(Trace)]82pub struct CompactFormat {83	pub resolver: PathResolver,84	pub padding: usize,85}8687impl TraceFormat for CompactFormat {88	fn write_trace(89		&self,90		out: &mut dyn std::fmt::Write,91		_s: &State,92		error: &LocError,93	) -> Result<(), std::fmt::Error> {94		write!(out, "{}", error.error())?;95		if let Error::ImportSyntaxError { path, error } = error.error() {96			use std::fmt::Write;9798			writeln!(out)?;99			let mut n = path.source_path().path().map_or_else(100				|| path.source_path().to_string(),101				|r| self.resolver.resolve(r),102			);103			let mut offset = error.location.offset;104			let is_eof = if offset >= path.code().len() {105				offset = path.code().len().saturating_sub(1);106				true107			} else {108				false109			};110			let mut location = path111				.map_source_locations(&[offset as u32])112				.into_iter()113				.next()114				.unwrap();115			if is_eof {116				location.column += 1;117			}118119			write!(n, ":").unwrap();120			print_code_location(&mut n, &location, &location).unwrap();121			write!(out, "{:<p$}{n}", "", p = self.padding)?;122		}123		let file_names = error124			.trace()125			.0126			.iter()127			.map(|el| &el.location)128			.map(|location| {129				use std::fmt::Write;130				#[allow(clippy::option_if_let_else)]131				if let Some(location) = location {132					let mut resolved_path = match location.0.source_path().path() {133						Some(r) => self.resolver.resolve(r),134						None => location.0.source_path().to_string(),135					};136					// TODO: Process all trace elements first137					let location = location.0.map_source_locations(&[location.1, location.2]);138					write!(resolved_path, ":").unwrap();139					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();140					write!(resolved_path, ":").unwrap();141					Some(resolved_path)142				} else {143					None144				}145			})146			.collect::<Vec<_>>();147		let align = file_names148			.iter()149			.flatten()150			.map(String::len)151			.max()152			.unwrap_or(0);153		for (el, file) in error.trace().0.iter().zip(file_names) {154			writeln!(out)?;155			if let Some(file) = file {156				write!(157					out,158					"{:<p$}{:<w$} {}",159					"",160					file,161					el.desc,162					p = self.padding,163					w = align164				)?;165			} else {166				write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;167			}168		}169		Ok(())170	}171}172173#[derive(Trace)]174pub struct JsFormat;175impl TraceFormat for JsFormat {176	fn write_trace(177		&self,178		out: &mut dyn std::fmt::Write,179		_s: &State,180		error: &LocError,181	) -> Result<(), std::fmt::Error> {182		write!(out, "{}", error.error())?;183		for item in &error.trace().0 {184			writeln!(out)?;185			let desc = &item.desc;186			if let Some(source) = &item.location {187				let start_end = source.0.map_source_locations(&[source.1, source.2]);188				let resolved_path = source.0.source_path().path().map_or_else(189					|| source.0.source_path().to_string(),190					|r| r.display().to_string(),191				);192193				write!(194					out,195					"    at {} ({}:{}:{})",196					desc, resolved_path, start_end[0].line, start_end[0].column,197				)?;198			} else {199				write!(out, "    during {desc}")?;200			}201		}202		Ok(())203	}204}205206/// rustc-like trace displaying207#[cfg(feature = "explaining-traces")]208#[derive(Trace)]209pub struct ExplainingFormat {210	pub resolver: PathResolver,211}212#[cfg(feature = "explaining-traces")]213impl TraceFormat for ExplainingFormat {214	fn write_trace(215		&self,216		out: &mut dyn std::fmt::Write,217		_s: &State,218		error: &LocError,219	) -> Result<(), std::fmt::Error> {220		write!(out, "{}", error.error())?;221		if let Error::ImportSyntaxError { path, error } = error.error() {222			writeln!(out)?;223			let offset = error.location.offset;224			let location = path225				.map_source_locations(&[offset as u32])226				.into_iter()227				.next()228				.unwrap();229			let mut end_location = location;230			end_location.offset += 1;231232			self.print_snippet(233				out,234				path.code(),235				path,236				&location,237				&end_location,238				"syntax error",239			)?;240		}241		let trace = &error.trace();242		for item in &trace.0 {243			writeln!(out)?;244			let desc = &item.desc;245			if let Some(source) = &item.location {246				let start_end = source.0.map_source_locations(&[source.1, source.2]);247				self.print_snippet(248					out,249					source.0.code(),250					&source.0,251					&start_end[0],252					&start_end[1],253					desc,254				)?;255			} else {256				write!(out, "{desc}")?;257			}258		}259		Ok(())260	}261}262263impl ExplainingFormat {264	fn print_snippet(265		&self,266		out: &mut dyn std::fmt::Write,267		source: &str,268		origin: &Source,269		start: &CodeLocation,270		end: &CodeLocation,271		desc: &str,272	) -> Result<(), std::fmt::Error> {273		use annotate_snippets::{274			display_list::{DisplayList, FormatOptions},275			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},276		};277278		let source_fragment: String = source279			.chars()280			.skip(start.line_start_offset)281			.take(end.line_end_offset - end.line_start_offset)282			.collect();283284		let origin = origin.source_path().path().map_or_else(285			|| origin.source_path().to_string(),286			|r| self.resolver.resolve(r),287		);288		let snippet = Snippet {289			opt: FormatOptions {290				color: true,291				..FormatOptions::default()292			},293			title: None,294			footer: vec![],295			slices: vec![Slice {296				source: &source_fragment,297				line_start: start.line,298				origin: Some(&origin),299				fold: false,300				annotations: vec![SourceAnnotation {301					label: desc,302					annotation_type: AnnotationType::Error,303					range: (304						start.offset - start.line_start_offset,305						(end.offset - start.line_start_offset).min(source_fragment.len()),306					),307				}],308			}],309		};310311		let dl = DisplayList::from(snippet);312		write!(out, "{dl}")?;313314		Ok(())315	}316}
after · crates/jrsonnet-evaluator/src/trace/mod.rs
1use std::path::{Path, PathBuf};23use jrsonnet_gcmodule::Trace;4use jrsonnet_parser::{CodeLocation, Source};56use crate::{error::Error, LocError, State};78/// The way paths should be displayed9#[derive(Clone, Trace)]10pub enum PathResolver {11	/// Only filename12	FileName,13	/// Absolute path14	Absolute,15	/// Path relative to base directory16	Relative(PathBuf),17}1819impl PathResolver {20	/// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure21	pub fn new_cwd_fallback() -> Self {22		std::env::current_dir().map_or(Self::Absolute, Self::Relative)23	}24	pub fn resolve(&self, from: &Path) -> String {25		match self {26			Self::FileName => from27				.file_name()28				.expect("file name exists")29				.to_string_lossy()30				.into_owned(),31			Self::Absolute => from.to_string_lossy().into_owned(),32			Self::Relative(base) => {33				if from.is_relative() {34					return from.to_string_lossy().into_owned();35				}36				pathdiff::diff_paths(from, base)37					.expect("base is absolute")38					.to_string_lossy()39					.into_owned()40			}41		}42	}43}4445/// Implements pretty-printing of traces46#[allow(clippy::module_name_repetitions)]47pub trait TraceFormat: Trace {48	fn write_trace(49		&self,50		out: &mut dyn std::fmt::Write,51		s: &State,52		error: &LocError,53	) -> Result<(), std::fmt::Error>;54}5556fn print_code_location(57	out: &mut impl std::fmt::Write,58	start: &CodeLocation,59	end: &CodeLocation,60) -> Result<(), std::fmt::Error> {61	if start.line == end.line {62		if start.column == end.column {63			write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;64		} else {65			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;66		}67	} else {68		write!(69			out,70			"{}:{}-{}:{}",71			start.line,72			end.column.saturating_sub(1),73			start.line,74			end.column75		)?;76	}77	Ok(())78}7980/// vanilla-like jsonnet formatting81#[derive(Trace)]82pub struct CompactFormat {83	pub resolver: PathResolver,84	pub padding: usize,85}8687impl TraceFormat for CompactFormat {88	fn write_trace(89		&self,90		out: &mut dyn std::fmt::Write,91		_s: &State,92		error: &LocError,93	) -> Result<(), std::fmt::Error> {94		write!(out, "{}", error.error())?;95		if let Error::ImportSyntaxError { path, error } = error.error() {96			use std::fmt::Write;9798			writeln!(out)?;99			let mut n = path.source_path().path().map_or_else(100				|| path.source_path().to_string(),101				|r| self.resolver.resolve(r),102			);103			let mut offset = error.location.offset;104			let is_eof = if offset >= path.code().len() {105				offset = path.code().len().saturating_sub(1);106				true107			} else {108				false109			};110			let mut location = path111				.map_source_locations(&[offset as u32])112				.into_iter()113				.next()114				.unwrap();115			if is_eof {116				location.column += 1;117			}118119			write!(n, ":").unwrap();120			print_code_location(&mut n, &location, &location).unwrap();121			write!(out, "{:<p$}{n}", "", p = self.padding)?;122		}123		let file_names = error124			.trace()125			.0126			.iter()127			.map(|el| &el.location)128			.map(|location| {129				use std::fmt::Write;130				#[allow(clippy::option_if_let_else)]131				if let Some(location) = location {132					let mut resolved_path = match location.0.source_path().path() {133						Some(r) => self.resolver.resolve(r),134						None => location.0.source_path().to_string(),135					};136					// TODO: Process all trace elements first137					let location = location.0.map_source_locations(&[location.1, location.2]);138					write!(resolved_path, ":").unwrap();139					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();140					write!(resolved_path, ":").unwrap();141					Some(resolved_path)142				} else {143					None144				}145			})146			.collect::<Vec<_>>();147		let align = file_names148			.iter()149			.flatten()150			.map(String::len)151			.max()152			.unwrap_or(0);153		for (el, file) in error.trace().0.iter().zip(file_names) {154			writeln!(out)?;155			if let Some(file) = file {156				write!(157					out,158					"{:<p$}{:<w$} {}",159					"",160					file,161					el.desc,162					p = self.padding,163					w = align164				)?;165			} else {166				write!(out, "{:<p$}{}", "", el.desc, p = self.padding,)?;167			}168		}169		Ok(())170	}171}172173#[derive(Trace)]174pub struct JsFormat;175impl TraceFormat for JsFormat {176	fn write_trace(177		&self,178		out: &mut dyn std::fmt::Write,179		_s: &State,180		error: &LocError,181	) -> Result<(), std::fmt::Error> {182		write!(out, "{}", error.error())?;183		for item in &error.trace().0 {184			writeln!(out)?;185			let desc = &item.desc;186			if let Some(source) = &item.location {187				let start_end = source.0.map_source_locations(&[source.1, source.2]);188				let resolved_path = source.0.source_path().path().map_or_else(189					|| source.0.source_path().to_string(),190					|r| r.display().to_string(),191				);192193				write!(194					out,195					"    at {} ({}:{}:{})",196					desc, resolved_path, start_end[0].line, start_end[0].column,197				)?;198			} else {199				write!(out, "    during {desc}")?;200			}201		}202		Ok(())203	}204}205206/// rustc-like trace displaying207#[cfg(feature = "explaining-traces")]208#[derive(Trace)]209pub struct ExplainingFormat {210	pub resolver: PathResolver,211}212#[cfg(feature = "explaining-traces")]213impl TraceFormat for ExplainingFormat {214	fn write_trace(215		&self,216		out: &mut dyn std::fmt::Write,217		_s: &State,218		error: &LocError,219	) -> Result<(), std::fmt::Error> {220		write!(out, "{}", error.error())?;221		if let Error::ImportSyntaxError { path, error } = error.error() {222			writeln!(out)?;223			let offset = error.location.offset;224			let location = path225				.map_source_locations(&[offset as u32])226				.into_iter()227				.next()228				.unwrap();229			let mut end_location = location;230			end_location.offset += 1;231232			self.print_snippet(233				out,234				path.code(),235				path,236				&location,237				&end_location,238				"syntax error",239			)?;240		}241		let trace = &error.trace();242		for item in &trace.0 {243			writeln!(out)?;244			let desc = &item.desc;245			if let Some(source) = &item.location {246				let start_end = source.0.map_source_locations(&[source.1, source.2]);247				self.print_snippet(248					out,249					source.0.code(),250					&source.0,251					&start_end[0],252					&start_end[1],253					desc,254				)?;255			} else {256				write!(out, "{desc}")?;257			}258		}259		Ok(())260	}261}262263impl ExplainingFormat {264	fn print_snippet(265		&self,266		out: &mut dyn std::fmt::Write,267		source: &str,268		origin: &Source,269		start: &CodeLocation,270		end: &CodeLocation,271		desc: &str,272	) -> Result<(), std::fmt::Error> {273		use annotate_snippets::{274			display_list::{DisplayList, FormatOptions},275			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},276		};277278		let source_fragment: String = source279			.chars()280			.skip(start.line_start_offset)281			.take(end.line_end_offset - end.line_start_offset)282			.collect();283284		let origin = origin.source_path().path().map_or_else(285			|| origin.source_path().to_string(),286			|r| self.resolver.resolve(r),287		);288		let snippet = Snippet {289			opt: FormatOptions {290				color: true,291				..FormatOptions::default()292			},293			title: None,294			footer: vec![],295			slices: vec![Slice {296				source: &source_fragment,297				line_start: start.line,298				origin: Some(&origin),299				fold: false,300				annotations: vec![SourceAnnotation {301					label: desc,302					annotation_type: AnnotationType::Error,303					range: (304						start.offset - start.line_start_offset,305						(end.offset.saturating_sub(start.line_start_offset))306							.min(source_fragment.len()),307					),308				}],309			}],310		};311312		let dl = DisplayList::from(snippet);313		write!(out, "{dl}")?;314315		Ok(())316	}317}