#![allow(clippy::question_mark)]
#[cfg(feature = "from-toml")]
use crate::Item;
use crate::{Key, Span};
use std::fmt::{self, Debug, Display};
#[derive(Clone, Copy)]
pub enum PathComponent<'de> {
Key(Key<'de>),
Index(usize),
}
#[repr(transparent)]
pub struct TomlPath<'a>([PathComponent<'a>]);
impl<'a> std::ops::Deref for TomlPath<'a> {
type Target = [PathComponent<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Debug for TomlPath<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = String::new();
push_toml_path(&mut out, &self.0);
Debug::fmt(&out, f)
}
}
impl Display for TomlPath<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = String::new();
push_toml_path(&mut out, &self.0);
f.write_str(&out)
}
}
fn is_bare_key(key: &str) -> bool {
if key.is_empty() {
return false;
}
for &b in key.as_bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-' => (),
_ => return false,
}
}
true
}
pub(crate) struct MaybeTomlPath {
ptr: std::ptr::NonNull<PathComponent<'static>>,
len: u32,
size: u32,
}
impl MaybeTomlPath {
pub(crate) fn empty() -> Self {
MaybeTomlPath {
ptr: std::ptr::NonNull::dangling(),
len: u32::MAX,
size: 0,
}
}
pub(crate) fn has_path(&self) -> bool {
self.size > 0
}
pub(crate) fn from_components(components: &[PathComponent<'_>]) -> MaybeTomlPath {
if components.is_empty() {
return Self::empty();
}
let len = components.len();
let mut total_string_bytes: usize = 0;
for comp in components {
if let PathComponent::Key(key) = comp {
total_string_bytes += key.name.len();
}
}
let comp_size = len * std::mem::size_of::<PathComponent<'static>>();
let size = comp_size + total_string_bytes;
let layout = std::alloc::Layout::from_size_align(
size,
std::mem::align_of::<PathComponent<'static>>(),
)
.unwrap();
let raw = unsafe { std::alloc::alloc(layout) };
if raw.is_null() {
std::alloc::handle_alloc_error(layout);
}
let base = raw.cast::<PathComponent<'static>>();
let mut string_cursor = unsafe { raw.add(comp_size) };
for (i, comp) in components.iter().enumerate() {
let stored = match comp {
PathComponent::Key(key) => {
let name_bytes = key.name.as_bytes();
let name_len = name_bytes.len();
unsafe {
std::ptr::copy_nonoverlapping(name_bytes.as_ptr(), string_cursor, name_len);
}
let name: &'static str = unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
string_cursor,
name_len,
))
};
string_cursor = unsafe { string_cursor.add(name_len) };
PathComponent::Key(Key {
name,
span: key.span,
})
}
PathComponent::Index(idx) => PathComponent::Index(*idx),
};
unsafe {
base.add(i).write(stored);
}
}
MaybeTomlPath {
ptr: unsafe { std::ptr::NonNull::new_unchecked(base) },
len: len as u32,
size: size as u32,
}
}
#[cfg(feature = "from-toml")]
#[inline(always)]
pub(crate) fn uncomputed(item_ptr: *const Item<'_>) -> Self {
MaybeTomlPath {
ptr: unsafe {
std::ptr::NonNull::new_unchecked(item_ptr as *mut PathComponent<'static>)
},
len: 0,
size: 0,
}
}
#[cfg(feature = "from-toml")]
pub(crate) fn is_uncomputed(&self) -> bool {
self.size == 0 && self.len != u32::MAX
}
#[cfg(feature = "from-toml")]
pub(crate) fn uncomputed_ptr(&self) -> *const () {
self.ptr.as_ptr() as *const ()
}
fn as_toml_path<'a>(&'a self) -> Option<&'a TomlPath<'a>> {
if !self.has_path() {
return None;
}
let slice = unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) };
Some(unsafe { &*(slice as *const [PathComponent<'static>] as *const TomlPath<'a>) })
}
}
impl Drop for MaybeTomlPath {
fn drop(&mut self) {
let size = self.size as usize;
if size > 0 {
let layout = std::alloc::Layout::from_size_align(
size,
std::mem::align_of::<PathComponent<'static>>(),
)
.unwrap();
unsafe {
std::alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
}
unsafe impl Send for MaybeTomlPath {}
unsafe impl Sync for MaybeTomlPath {}
pub struct Error {
pub(crate) kind: ErrorInner,
pub(crate) span: Span,
pub(crate) path: MaybeTomlPath,
}
pub(crate) enum ErrorInner {
Static(ErrorKind<'static>),
Custom(Box<str>),
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum ErrorKind<'a> {
Custom(&'a str),
UnexpectedEof,
FileTooLarge,
InvalidCharInString(char),
InvalidEscape(char),
InvalidHexEscape(char),
InvalidEscapeValue(u32),
Unexpected(char),
UnterminatedString(char),
InvalidInteger(&'static str),
InvalidFloat(&'static str),
InvalidDateTime(&'static str),
OutOfRange {
ty: &'static &'static str,
range: &'static &'static str,
},
Wanted {
expected: &'static &'static str,
found: &'static &'static str,
},
DuplicateTable {
name: Span,
first: Span,
},
DuplicateKey {
first: Span,
},
RedefineAsArray {
first: Span,
},
MultilineStringKey,
DottedKeyInvalidType {
first: Span,
},
UnexpectedKey {
tag: u32,
},
UnquotedString,
MissingField(&'static str),
DuplicateField {
field: &'static str,
first: Span,
},
Deprecated {
tag: u32,
old: &'static &'static str,
new: &'static &'static str,
},
UnexpectedValue {
expected: &'static [&'static str],
},
UnexpectedVariant {
expected: &'static [&'static str],
},
MissingArrayComma,
UnclosedArray,
MissingInlineTableComma,
UnclosedInlineTable,
}
impl<'a> ErrorKind<'a> {
pub fn kind_name(&self) -> &'static str {
match self {
ErrorKind::Custom(_) => "Custom",
ErrorKind::UnexpectedEof => "UnexpectedEof",
ErrorKind::FileTooLarge => "FileTooLarge",
ErrorKind::InvalidCharInString(_) => "InvalidCharInString",
ErrorKind::InvalidEscape(_) => "InvalidEscape",
ErrorKind::InvalidHexEscape(_) => "InvalidHexEscape",
ErrorKind::InvalidEscapeValue(_) => "InvalidEscapeValue",
ErrorKind::Unexpected(_) => "Unexpected",
ErrorKind::UnterminatedString(_) => "UnterminatedString",
ErrorKind::InvalidInteger(_) => "InvalidInteger",
ErrorKind::InvalidFloat(_) => "InvalidFloat",
ErrorKind::InvalidDateTime(_) => "InvalidDateTime",
ErrorKind::OutOfRange { .. } => "OutOfRange",
ErrorKind::Wanted { .. } => "Wanted",
ErrorKind::DuplicateTable { .. } => "DuplicateTable",
ErrorKind::DuplicateKey { .. } => "DuplicateKey",
ErrorKind::RedefineAsArray { .. } => "RedefineAsArray",
ErrorKind::MultilineStringKey => "MultilineStringKey",
ErrorKind::DottedKeyInvalidType { .. } => "DottedKeyInvalidType",
ErrorKind::UnexpectedKey { .. } => "UnexpectedKey",
ErrorKind::UnquotedString => "UnquotedString",
ErrorKind::MissingField(_) => "MissingField",
ErrorKind::DuplicateField { .. } => "DuplicateField",
ErrorKind::Deprecated { .. } => "Deprecated",
ErrorKind::UnexpectedValue { .. } => "UnexpectedValue",
ErrorKind::UnexpectedVariant { .. } => "UnexpectedVariant",
ErrorKind::MissingArrayComma => "MissingArrayComma",
ErrorKind::UnclosedArray => "UnclosedArray",
ErrorKind::MissingInlineTableComma => "MissingInlineTableComma",
ErrorKind::UnclosedInlineTable => "UnclosedInlineTable",
}
}
}
impl Error {
pub fn span(&self) -> Span {
self.span
}
pub fn kind(&self) -> ErrorKind<'_> {
match &self.kind {
ErrorInner::Static(kind) => *kind,
ErrorInner::Custom(error) => ErrorKind::Custom(error),
}
}
pub fn path<'a>(&'a self) -> Option<&'a TomlPath<'a>> {
self.path.as_toml_path()
}
pub fn custom(message: impl ToString, span: Span) -> Error {
Error {
kind: ErrorInner::Custom(message.to_string().into()),
span,
path: MaybeTomlPath::empty(),
}
}
#[cfg(feature = "from-toml")]
pub(crate) fn custom_static(message: &'static str, span: Span) -> Error {
Error {
kind: ErrorInner::Static(ErrorKind::Custom(message)),
span,
path: MaybeTomlPath::empty(),
}
}
pub(crate) fn new(kind: ErrorKind<'static>, span: Span) -> Error {
Error {
kind: ErrorInner::Static(kind),
span,
path: MaybeTomlPath::empty(),
}
}
pub(crate) fn new_with_path(kind: ErrorKind<'static>, span: Span, path: MaybeTomlPath) -> Self {
Error {
kind: ErrorInner::Static(kind),
span,
path,
}
}
}
impl<'a> Debug for ErrorKind<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.kind_name())
}
}
fn kind_message(kind: ErrorKind<'_>) -> String {
let mut out = String::new();
kind_message_inner(kind, &mut out);
out
}
#[inline(never)]
fn s_push(out: &mut String, s: &str) {
out.push_str(s);
}
#[inline(never)]
fn s_push_char(out: &mut String, c: char) {
out.push(c);
}
fn push_escape(out: &mut String, c: char) {
if c.is_control() {
for esc in c.escape_default() {
s_push_char(out, esc);
}
} else {
s_push_char(out, c);
}
}
fn push_u32(out: &mut String, mut n: u32) {
let mut buf = [0u8; 10];
let mut i = buf.len();
if n == 0 {
s_push_char(out, '0');
return;
}
while n > 0 {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
}
s_push(out, unsafe { std::str::from_utf8_unchecked(&buf[i..]) });
}
fn kind_message_inner(kind: ErrorKind<'_>, out: &mut String) {
match kind {
ErrorKind::Custom(message) => s_push(out, message),
ErrorKind::UnexpectedEof => s_push(out, "unexpected eof encountered"),
ErrorKind::FileTooLarge => s_push(out, "file is too large (maximum 512 MiB)"),
ErrorKind::InvalidCharInString(c) => {
s_push(out, "invalid character in string: `");
push_escape(out, c);
s_push_char(out, '`');
}
ErrorKind::InvalidEscape(c) => {
s_push(out, "invalid escape character in string: `");
push_escape(out, c);
s_push_char(out, '`');
}
ErrorKind::InvalidHexEscape(c) => {
s_push(out, "invalid hex escape character in string: `");
push_escape(out, c);
s_push_char(out, '`');
}
ErrorKind::InvalidEscapeValue(c) => {
s_push(out, "invalid unicode escape value `");
push_unicode_escape(out, c);
s_push_char(out, '`');
}
ErrorKind::Unexpected(c) => {
s_push(out, "unexpected character found: `");
push_escape(out, c);
s_push_char(out, '`');
}
ErrorKind::UnterminatedString(delim) => {
if delim == '\'' {
s_push(out, "unterminated literal string, expected `'`");
} else {
s_push(out, "unterminated basic string, expected `\"`");
}
}
ErrorKind::Wanted { expected, found } => {
s_push(out, "expected ");
s_push(out, expected);
s_push(out, ", found ");
s_push(out, found);
}
ErrorKind::InvalidInteger(reason)
| ErrorKind::InvalidFloat(reason)
| ErrorKind::InvalidDateTime(reason) => {
let prefix = match kind {
ErrorKind::InvalidInteger(_) => "invalid integer",
ErrorKind::InvalidFloat(_) => "invalid float",
_ => "invalid datetime",
};
s_push(out, prefix);
if !reason.is_empty() {
s_push(out, ": ");
s_push(out, reason);
}
}
ErrorKind::OutOfRange { ty, .. } => {
s_push(out, "value out of range for ");
s_push(out, ty);
}
ErrorKind::DuplicateTable { .. } => s_push(out, "redefinition of table"),
ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
ErrorKind::RedefineAsArray { .. } => s_push(out, "table redefined as array"),
ErrorKind::MultilineStringKey => {
s_push(out, "multiline strings are not allowed for key");
}
ErrorKind::DottedKeyInvalidType { .. } => {
s_push(out, "dotted key attempted to extend non-table type");
}
ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
ErrorKind::UnquotedString => {
s_push(out, "string values must be quoted, expected string literal");
}
ErrorKind::MissingField(field) => {
s_push(out, "missing required key '");
s_push(out, field);
s_push_char(out, '\'');
}
ErrorKind::DuplicateField { field, .. } => {
s_push(out, "duplicate key '");
s_push(out, field);
s_push_char(out, '\'');
}
ErrorKind::Deprecated { old, new, .. } => {
s_push(out, "key '");
s_push(out, old);
s_push(out, "' is deprecated, use '");
s_push(out, new);
s_push(out, "' instead");
}
ErrorKind::UnexpectedValue { expected } => {
s_push(out, "unexpected value, expected one of: ");
let mut first = true;
for val in expected {
if !first {
s_push(out, ", ");
}
first = false;
s_push(out, val);
}
}
ErrorKind::UnexpectedVariant { expected } => {
s_push(out, "unknown variant, expected one of: ");
let mut first = true;
for val in expected {
if !first {
s_push(out, ", ");
}
first = false;
s_push(out, val);
}
}
ErrorKind::MissingArrayComma => {
s_push(out, "missing comma between elements, expected `,` in array");
}
ErrorKind::UnclosedArray => s_push(out, "unclosed array, expected `]`"),
ErrorKind::MissingInlineTableComma => {
s_push(
out,
"missing comma between fields, expected `,` in inline table",
);
}
ErrorKind::UnclosedInlineTable => s_push(out, "unclosed inline table, expected `}`"),
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&kind_message(self.kind()))?;
if let Some(path) = self.path() {
let components: &[PathComponent<'_>] = path;
let display = match self.kind() {
ErrorKind::DuplicateField { .. } | ErrorKind::Deprecated { .. } => {
&components[..components.len().saturating_sub(1)]
}
_ => components,
};
if !display.is_empty() {
f.write_str(" at `")?;
let mut out = String::new();
push_toml_path(&mut out, display);
f.write_str(&out)?;
f.write_str("`")?;
}
}
Ok(())
}
}
impl Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = self.kind();
f.debug_struct("Error")
.field("kind", &kind.kind_name())
.field("message", &kind_message(kind))
.field("span", &self.span().range())
.field("path", &self.path())
.finish()
}
}
impl std::error::Error for Error {}
impl From<(ErrorKind<'static>, Span)> for Error {
fn from((kind, span): (ErrorKind<'static>, Span)) -> Self {
Self {
kind: ErrorInner::Static(kind),
span,
path: MaybeTomlPath::empty(),
}
}
}
fn push_toml_path(out: &mut String, path: &[PathComponent<'_>]) {
let mut first = true;
for component in path.iter() {
match component {
PathComponent::Key(key) => {
if !first {
s_push_char(out, '.');
}
first = false;
if is_bare_key(key.name) {
s_push(out, key.name);
} else {
s_push_char(out, '"');
for ch in key.name.chars() {
match ch {
'"' => s_push(out, "\\\""),
'\\' => s_push(out, "\\\\"),
'\n' => s_push(out, "\\n"),
'\r' => s_push(out, "\\r"),
'\t' => s_push(out, "\\t"),
c if c.is_control() => {
push_unicode_escape(out, c as u32);
}
c => s_push_char(out, c),
}
}
s_push_char(out, '"');
}
}
PathComponent::Index(idx) => {
s_push_char(out, '[');
push_u32(out, *idx as u32);
s_push_char(out, ']');
}
}
}
}
fn push_unicode_escape(out: &mut String, n: u32) {
s_push(out, "\\u");
let mut buf = [0u8; 8];
let digits = if n <= 0xFFFF { 4 } else { 6 };
for i in (0..digits).rev() {
let nibble = ((n >> (i * 4)) & 0xF) as u8;
buf[digits - 1 - i] = if nibble < 10 {
b'0' + nibble
} else {
b'A' + nibble - 10
};
}
s_push(out, unsafe {
std::str::from_utf8_unchecked(&buf[..digits])
});
}
impl Error {
pub fn message(&self, source: &str) -> String {
let mut out = String::new();
self.message_inner(source, &mut out);
out
}
pub fn message_with_path(&self, source: &str) -> String {
let mut out = String::new();
self.message_inner(source, &mut out);
self.append_path(&mut out);
out
}
fn append_path(&self, out: &mut String) {
if let Some(p) = self.path() {
let components: &[PathComponent<'_>] = p;
let display = match self.kind() {
ErrorKind::DuplicateKey { .. }
| ErrorKind::DuplicateTable { .. }
| ErrorKind::DuplicateField { .. }
| ErrorKind::Deprecated { .. } => &components[..components.len().saturating_sub(1)],
_ => components,
};
if !display.is_empty() {
s_push(out, " at `");
push_toml_path(out, display);
s_push_char(out, '`');
}
}
}
fn message_inner(&self, source: &str, out: &mut String) {
let span = self.span;
let kind = self.kind();
let path = self.path();
match kind {
ErrorKind::DuplicateKey { .. } => {
if let Some(name) = source.get(span.range()) {
s_push(out, "the key `");
s_push(out, name);
s_push(out, "` is defined multiple times in table");
} else {
kind_message_inner(kind, out);
}
}
ErrorKind::DuplicateTable { name, .. } => {
if let Some(table_name) = source.get(name.range()) {
s_push(out, "redefinition of table `");
s_push(out, table_name);
s_push_char(out, '`');
} else {
kind_message_inner(kind, out);
}
}
ErrorKind::UnexpectedKey { .. } if path.is_none() => {
if let Some(key_name) = source.get(span.range()) {
s_push(out, "unexpected key `");
s_push(out, key_name);
s_push_char(out, '`');
} else {
kind_message_inner(kind, out);
}
}
ErrorKind::DuplicateField { field, .. } => {
if let Some(key_name) = source.get(span.range()) {
if key_name == field {
s_push(out, "key '");
s_push(out, field);
s_push(out, "' defined multiple times in same table");
} else {
s_push(out, "both '");
s_push(out, key_name);
s_push(out, "' and '");
s_push(out, field);
s_push(out, "' are defined but resolve to the same field");
}
} else {
kind_message_inner(kind, out);
}
}
ErrorKind::UnexpectedVariant { .. } => {
if let Some(value) = source.get(span.range()) {
s_push(out, "unknown variant ");
match value.split_once('\n') {
Some((first, _)) => {
s_push(out, first);
s_push(out, "...");
}
None => s_push(out, value),
}
} else {
kind_message_inner(kind, out);
}
}
_ => kind_message_inner(kind, out),
}
}
pub fn primary_label(&self) -> Option<(Span, String)> {
let mut out = String::new();
self.primary_label_inner(&mut out);
Some((self.span, out))
}
fn primary_label_inner(&self, out: &mut String) {
let kind = self.kind();
match kind {
ErrorKind::DuplicateKey { .. } => s_push(out, "duplicate key"),
ErrorKind::DuplicateTable { .. } => s_push(out, "duplicate table"),
ErrorKind::DottedKeyInvalidType { .. } => {
s_push(out, "attempted to extend table here");
}
ErrorKind::Unexpected(c) => {
s_push(out, "unexpected character '");
push_escape(out, c);
s_push_char(out, '\'');
}
ErrorKind::InvalidCharInString(c) => {
s_push(out, "invalid character '");
push_escape(out, c);
s_push(out, "' in string");
}
ErrorKind::InvalidEscape(c) => {
s_push(out, "invalid escape character '");
push_escape(out, c);
s_push(out, "' in string");
}
ErrorKind::InvalidEscapeValue(_) => s_push(out, "invalid unicode escape value"),
ErrorKind::InvalidInteger(_)
| ErrorKind::InvalidFloat(_)
| ErrorKind::InvalidDateTime(_) => kind_message_inner(kind, out),
ErrorKind::InvalidHexEscape(c) => {
s_push(out, "invalid hex escape '");
push_escape(out, c);
s_push_char(out, '\'');
}
ErrorKind::Wanted { expected, .. } => {
s_push(out, "expected ");
s_push(out, expected);
}
ErrorKind::MultilineStringKey => s_push(out, "multiline keys are not allowed"),
ErrorKind::UnterminatedString(delim) => {
s_push(out, "expected `");
s_push_char(out, delim);
s_push_char(out, '`');
}
ErrorKind::UnquotedString => s_push(out, "string is not quoted"),
ErrorKind::UnexpectedKey { .. } => s_push(out, "unexpected key"),
ErrorKind::MissingField(field) => {
s_push(out, "missing key '");
s_push(out, field);
s_push_char(out, '\'');
}
ErrorKind::DuplicateField { .. } => s_push(out, "duplicate key"),
ErrorKind::Deprecated { .. } => s_push(out, "deprecated key"),
ErrorKind::UnexpectedValue { .. } => s_push(out, "unexpected value"),
ErrorKind::UnexpectedVariant { expected } => {
s_push(out, "expected one of: ");
let mut first = true;
for val in expected {
if !first {
s_push(out, ", ");
}
first = false;
s_push(out, val);
}
}
ErrorKind::MissingArrayComma => s_push(out, "expected `,`"),
ErrorKind::UnclosedArray => s_push(out, "expected `]`"),
ErrorKind::MissingInlineTableComma => s_push(out, "expected `,`"),
ErrorKind::UnclosedInlineTable => s_push(out, "expected `}`"),
ErrorKind::OutOfRange { range, .. } => {
if !range.is_empty() {
s_push(out, "expected ");
s_push(out, range);
}
}
ErrorKind::UnexpectedEof
| ErrorKind::RedefineAsArray { .. }
| ErrorKind::FileTooLarge
| ErrorKind::Custom(..) => {}
}
}
pub fn secondary_label(&self) -> Option<(Span, String)> {
let (first, text) = match self.kind() {
ErrorKind::DuplicateKey { first } => (first, "first key instance"),
ErrorKind::DuplicateTable { first, .. } => (first, "first table instance"),
ErrorKind::DottedKeyInvalidType { first } => (first, "non-table"),
ErrorKind::RedefineAsArray { first } => (first, "first defined as table"),
ErrorKind::DuplicateField { first, .. } => (first, "first defined here"),
_ => return None,
};
Some((first, String::from(text)))
}
}