1 Commits

Author SHA1 Message Date
John Turner
17684d1742 debugging 2025-11-14 20:38:31 +00:00
154 changed files with 625 additions and 7475 deletions

View File

@@ -1,35 +0,0 @@
FROM gentoo/stage3:latest
USER root
# Fix permissions for distfiles directory
RUN mkdir -p /var/cache/distfiles && \
chmod 755 /var/cache/distfiles && \
chown -R portage:portage /var/cache/distfiles
# enable bintoo
RUN echo "BINPKG_FORMAT=\"gpkg\"" >> /etc/portage/make.conf
RUN echo "EMERGE_DEFAULT_OPTS=\"\${EMERGE_DEFAULT_OPTS} --getbinpkg\"" >> /etc/portage/make.conf
RUN echo "FEATURES=\"getbinpkg\"" >> /etc/portage/make.conf
RUN getuto
RUN emerge-webrsync && \
emerge --sync
# get super latest meson
RUN echo "=dev-build/meson-9999 **" >> /etc/portage/package.accept_keywords/package.accept
RUN emerge --ask=n =dev-build/meson-9999
RUN meson --version
# install nightly rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly
RUN source "$HOME/.cargo/env" && rustc --version && cargo --version
RUN emerge --ask=n dev-vcs/git
# Set working directory
WORKDIR /workspace
# Default command
CMD ["/bin/bash"]

View File

@@ -1,41 +0,0 @@
# name: Build gentoo-utils docker image
# on:
# push:
# branches:
# - master
# paths:
# - '.docker/Dockerfile'
# pull_request:
# paths:
# - '.docker/Dockerfile'
# workflow_dispatch:
# jobs:
# build-docker:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repo
# uses: actions/checkout@v4
# - name: Set up Docker buildx
# uses: docker/setup-buildx-action@v3
# - name: Log in to Github Container Registry
# uses: docker/login-action@v3
# with:
# registry: ${{ secrets.PIPELINE_REGISTRY_URL }}
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
# - name: Extract metadata for Docker
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: ghcr.op/${{ github.repository_owner }}/gentoo-utils-builder
# tags: |
# type=raw,value=latest
# type=sha,prefix={{branch}}-
# -name: Build and push docker image

4
.gitignore vendored
View File

@@ -1,5 +1 @@
/target /target
/subprojects
!/subprojects
/subprojects/*
!/subprojects/thiserror

2
Cargo.lock generated
View File

@@ -40,7 +40,7 @@ dependencies = [
[[package]] [[package]]
name = "mon" name = "mon"
version = "0.1.0" version = "0.1.0"
source = "git+https://jturnerusa.dev/cgit/mon/?rev=67861a4df8a5abdd70651d47cf265b20c41d2acc#67861a4df8a5abdd70651d47cf265b20c41d2acc" source = "git+https://jturnerusa.dev/cgit/mon/?rev=e6c5335d43bfbf2fffa3d3c44cde404c970e5ee6#e6c5335d43bfbf2fffa3d3c44cde404c970e5ee6"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"

View File

@@ -4,7 +4,10 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
mon = { git = "https://jturnerusa.dev/cgit/mon/", rev = "67861a4df8a5abdd70651d47cf265b20c41d2acc" } mon = { git = "https://jturnerusa.dev/cgit/mon/", rev = "e6c5335d43bfbf2fffa3d3c44cde404c970e5ee6" }
get = { git = "https://jturnerusa.dev/cgit/get/", rev = "cd5f75b65777a855ab010c3137304ac05f2e56b8" } get = { git = "https://jturnerusa.dev/cgit/get/", rev = "cd5f75b65777a855ab010c3137304ac05f2e56b8" }
itertools = "0.14.0" itertools = "0.14.0"
thiserror = "2.0.17" thiserror = "2.0.17"
[profile.dev]
debug = true

View File

@@ -1,22 +0,0 @@
#!/bin/bash
source /etc/profile
source /lib/gentoo/functions.sh
export PATH="${HOME}/.local/bin:${PATH}" CC=clang CXX=clang++
lld=$(command -v lld)
if [[ -n ${ldd} ]]; then
export LDFLAGS=-fuse-ld=${lld}
fi
if [[ ! -d build ]]; then
meson setup -Dfuzz=enabled -Dtests=enabled -Dbuildtype=debugoptimized -Ddocs=enabled build || exit $?
fi
meson compile -C build || exit $?
ebegin "running check commands"
parallel --halt soon,fail=1 --keep-order -j$(nproc) < check_commands.txt
eend $? || exit $?

View File

@@ -1,5 +0,0 @@
/usr/bin/meson format --recursive --check-only
rustfmt --edition 2024 --check $(find src -type f -name '*.rs')
ninja rustdoc -C build
ninja clippy -C build
meson test unittests doctests '*repo*' '*porthole*' -C build

View File

@@ -1,2 +0,0 @@
subdir('parser')
subdir('vercmp')

View File

@@ -1,100 +0,0 @@
use core::slice;
use gentoo_utils::{Parseable, atom::Atom};
use mon::{Parser, ParserFinishedError, input::InputIter};
use std::{
io::{BufRead, BufReader, Write},
process::{ChildStdin, ChildStdout, Command, Stdio},
sync::{LazyLock, Mutex},
};
struct PyProcess {
stdin: Mutex<ChildStdin>,
stdout: Mutex<BufReader<ChildStdout>>,
buffer: Mutex<String>,
}
#[allow(clippy::missing_safety_doc, clippy::needless_return)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn LLVMFuzzerTestOneInput(input: *const u8, len: usize) -> i32 {
static PY_PROCESS: LazyLock<PyProcess> = LazyLock::new(|| {
#[allow(clippy::zombie_processes)]
let mut proc = Command::new("atom.py")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.expect("failed to spawn atom.py");
let stdin = Mutex::new(proc.stdin.take().unwrap());
let stdout = Mutex::new(BufReader::new(proc.stdout.take().unwrap()));
PyProcess {
stdin,
stdout,
buffer: Mutex::new(String::new()),
}
});
let slice = unsafe { slice::from_raw_parts(input, len) };
if slice.iter().any(|b| !b.is_ascii_graphic()) {
return -1;
}
let str = match str::from_utf8(slice) {
Ok(str) => str,
Err(_) => return -1,
};
let atom = str.trim();
let mut stdin = PY_PROCESS.stdin.lock().expect("failed to get stdin lock");
writeln!(&mut stdin, "{atom}").expect("failed to write to python stdin");
let mut stdout = PY_PROCESS.stdout.lock().expect("failed to get stdout lock");
let mut buffer = PY_PROCESS.buffer.lock().expect("failed to get buffer lock");
buffer.clear();
stdout
.read_line(&mut buffer)
.expect("failed to readline from python");
let portage_result = match buffer.as_str().trim() {
"0" => true,
"1" => false,
result => panic!("got unexpected result from python: {result}"),
};
let gentoo_utils_result = Atom::parser().parse_finished(InputIter::new(atom));
match (portage_result, gentoo_utils_result) {
(true, Ok(_)) => {
eprintln!("agreement that {atom} is valid");
}
(false, Err(_)) => {
eprintln!("agreement that {atom} is invalid");
}
(true, Err(ParserFinishedError::Err(it) | ParserFinishedError::Unfinished(it))) => {
panic!("rejected valid atom: {atom}: {}", it.rest());
}
(false, Ok(atom))
if atom.usedeps().iter().any(|usedep| {
atom.usedeps()
.iter()
.filter(|u| usedep.flag() == u.flag())
.count()
> 1
}) =>
{
eprintln!("disagreement due to duplicates in usedeps");
}
(false, Ok(_)) => {
panic!("accpeted invalid atom: {atom}")
}
}
return 0;
}

View File

@@ -1,64 +0,0 @@
use std::{
env,
error::Error,
fs::{self, OpenOptions},
io::Write,
path::PathBuf,
};
use gentoo_utils::{
atom::Atom,
repo::{Repo, ebuild::Depend},
};
fn main() -> Result<(), Box<dyn Error>> {
let corpus_dir = PathBuf::from(
env::args()
.nth(1)
.expect("expected corpus directory as first argument"),
);
fs::create_dir_all(&corpus_dir)?;
let repo = Repo::new("/var/db/repos/gentoo");
let mut atoms = Vec::new();
for category in repo.categories()? {
for ebuild in category?.ebuilds()? {
let depend = ebuild?.depend().to_vec();
for expr in depend {
walk_expr(&mut atoms, &expr);
}
}
}
for (i, atom) in atoms.iter().enumerate() {
let path = corpus_dir.as_path().join(i.to_string());
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
write!(file, "{atom}")?;
}
Ok(())
}
fn walk_expr(atoms: &mut Vec<Atom>, depend: &Depend<Atom>) {
match depend {
Depend::Element(atom) => {
atoms.push(atom.clone());
}
Depend::AllOf(exprs)
| Depend::OneOf(exprs)
| Depend::AnyOf(exprs)
| Depend::ConditionalGroup(_, exprs) => {
for expr in exprs {
walk_expr(atoms, expr);
}
}
}
}

View File

@@ -1,6 +0,0 @@
fuzzers += {
'atom_parser': [
meson.current_source_dir() / 'gencorpus.rs',
meson.current_source_dir() / 'fuzz.rs',
],
}

View File

@@ -1,114 +0,0 @@
use core::slice;
use gentoo_utils::{
Parseable,
atom::{Atom, Version},
};
use mon::{Parser, ParserFinishedError, input::InputIter};
use std::{
cmp::Ordering,
io::{BufRead, BufReader, Write},
process::{ChildStdin, ChildStdout, Command, Stdio},
sync::{LazyLock, Mutex},
};
struct PyProcess {
stdin: Mutex<ChildStdin>,
stdout: Mutex<BufReader<ChildStdout>>,
buffer: Mutex<String>,
}
#[allow(clippy::missing_safety_doc, clippy::needless_return)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn LLVMFuzzerTestOneInput(input: *const u8, len: usize) -> i32 {
static PY_PROCESS: LazyLock<PyProcess> = LazyLock::new(|| {
#[allow(clippy::zombie_processes)]
let mut proc = Command::new("vercmp.py")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.expect("failed to spawn vercmp.py");
let stdin = Mutex::new(proc.stdin.take().unwrap());
let stdout = Mutex::new(BufReader::new(proc.stdout.take().unwrap()));
PyProcess {
stdin,
stdout,
buffer: Mutex::new(String::new()),
}
});
let control = Version::parser()
.parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1"))
.unwrap();
let slice = unsafe { slice::from_raw_parts(input, len) };
if slice.iter().any(|b| !b.is_ascii_graphic()) {
return -1;
}
let str = match str::from_utf8(slice) {
Ok(str) => str,
Err(_) => return -1,
};
let version_str = match str.split_ascii_whitespace().next() {
Some(lhs) => lhs,
None => return -1,
};
let version = match Version::parser().parse_finished(InputIter::new(version_str)) {
Ok(a) => a,
Err(_) => return -1,
};
if version.build_id().is_some() {
return -1;
}
let gentoo_utils = control.cmp(&version);
let portage_result = portage_vercmp(&PY_PROCESS, &control, &version);
match portage_result {
Ok(portage) if portage == gentoo_utils => {
eprintln!("agreement on {control} cmp {version} == {portage:?}");
}
Ok(portage) => {
panic!(
"disagreement on {control} == {version}:\nportage:{portage:?} gentoo_utils:{gentoo_utils:?}"
)
}
Err(_) => {
panic!("parsed invalid versions: {control} | {version}")
}
}
return 0;
}
fn portage_vercmp(pyproc: &PyProcess, a: &Version, b: &Version) -> Result<Ordering, ()> {
let mut stdin = pyproc.stdin.lock().expect("failed to get stdin lock");
let mut stdout = pyproc.stdout.lock().expect("failed to get stdout lock");
let mut buffer = pyproc.buffer.lock().expect("failed to get buffer lock");
writeln!(&mut stdin, "{a} {b}").expect("failed to write line to python process");
stdin.flush().unwrap();
buffer.clear();
stdout
.read_line(&mut buffer)
.expect("failed to read line from python process");
match buffer.as_str().trim() {
"0" => Ok(Ordering::Equal),
"1" => Ok(Ordering::Greater),
"-1" => Ok(Ordering::Less),
"err" => Err(()),
other => panic!("unexpected result from python: {other}"),
}
}

View File

@@ -1,43 +0,0 @@
use std::{
env,
error::Error,
fs::{self, OpenOptions},
io::Write,
path::PathBuf,
};
use gentoo_utils::repo::Repo;
fn main() -> Result<(), Box<dyn Error>> {
let corpus_dir = PathBuf::from(
env::args()
.nth(1)
.expect("expected corpus directory as first argument"),
);
fs::create_dir_all(&corpus_dir)?;
let repo = Repo::new("/var/db/repos/gentoo");
let mut versions = Vec::new();
for category in repo.categories()? {
for ebuild in category?.ebuilds()? {
let version = ebuild?.version().clone();
versions.push(version);
}
}
for (i, version) in versions.iter().enumerate() {
let path = corpus_dir.as_path().join(i.to_string());
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(path)?;
write!(file, "{version}")?;
}
Ok(())
}

View File

@@ -1,6 +0,0 @@
fuzzers += {
'atom_vercmp': [
meson.current_source_dir() / 'gencorpus.rs',
meson.current_source_dir() / 'fuzz.rs',
],
}

View File

@@ -1,59 +0,0 @@
cbindgen = find_program('cbindgen')
fuzzers = {}
subdir('atom')
foreach fuzzer, sources : fuzzers
gencorpus_rs = sources[0]
fuzz_rs = sources[1]
gencorpus = executable(
fuzzer + '_' + 'gencorpus',
gencorpus_rs,
dependencies: [mon],
link_with: [gentoo_utils],
)
corpus_directory = fuzzer + '_' + 'corpus'
corpus = custom_target(
fuzzer + '_' + 'corpus',
output: fuzzer + '_' + 'corpus',
command: [gencorpus, corpus_directory],
)
fuzz_h = custom_target(
fuzzer + '_' + 'fuzz_h',
input: fuzz_rs,
output: fuzzer + '_' + 'fuzz.h',
command: [cbindgen, '@INPUT@', '-o', '@OUTPUT'],
)
fuzz_rs = static_library(
fuzzer + '.rs',
fuzz_rs,
rust_abi: 'c',
rust_args: [
'-Cpasses=sancov-module',
'-Cllvm-args=-sanitizer-coverage-level=3',
'-Cllvm-args=-sanitizer-coverage-inline-8bit-counters',
],
dependencies: [mon],
link_with: [gentoo_utils],
)
fuzz = executable(
fuzzer + '_' + 'fuzzer',
link_args: ['-fsanitize=fuzzer'],
link_with: [fuzz_rs],
)
test(
fuzzer + '_' + 'fuzz',
fuzz,
args: [corpus_directory],
depends: [corpus],
timeout: 0,
)
endforeach

View File

@@ -1,40 +0,0 @@
project(
'gentoo-utils',
['rust', 'cpp'],
meson_version: '>=1.9.1',
default_options: {'rust_std': '2024', 'rust_nightly': 'enabled'},
)
rust = import('rust')
fs = import('fs')
mon = dependency('mon-0.1-rs')
get = dependency('get-0.1-rs')
itertools = dependency('itertools-0.14-rs')
thiserror = subproject('thiserror').get_variable('thiserror')
gentoo_utils = static_library(
'gentoo_utils',
'src/lib.rs',
dependencies: [mon, get, itertools],
link_with: [thiserror],
)
if get_option('tests').enabled()
rust.test('unittests', gentoo_utils)
subdir('tests')
endif
if get_option('fuzz').enabled()
subdir('fuzz')
endif
if get_option('docs').enabled()
rust.doctest(
'doctests',
gentoo_utils,
dependencies: [mon, get, itertools],
link_with: [thiserror],
args: ['--nocapture'],
)
endif

View File

@@ -1,3 +0,0 @@
option('fuzz', type: 'feature', value: 'disabled')
option('tests', type: 'feature', value: 'disabled')
option('docs', type: 'feature', value: 'disabled')

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env python3
import sys
from portage.dep import Atom
for line in sys.stdin.buffer:
try:
Atom(line.decode().strip())
sys.stdout.buffer.write(b"0\n")
except:
sys.stdout.buffer.write(b"1\n")
finally:
sys.stdout.buffer.flush()

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env python3
import sys
import portage
for line in sys.stdin.buffer:
a, b = line.decode().split(" ")
try:
sys.stdout.buffer.write(f"{portage.vercmp(a, b)}\n".encode())
except:
sys.stdout.buffer.write(b"err\n")
finally:
sys.stdout.buffer.flush()

View File

@@ -10,15 +10,15 @@ use get::Get;
use itertools::Itertools; use itertools::Itertools;
mod parsers; pub mod parsers;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Blocker { pub enum Blocker {
Weak, Weak,
Strong, Strong,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VersionOperator { pub enum VersionOperator {
Lt, Lt,
Gt, Gt,
@@ -28,19 +28,19 @@ pub enum VersionOperator {
Roughly, Roughly,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Category(#[get(method = "get", kind = "deref")] String); pub struct Category(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Name(#[get(method = "get", kind = "deref")] String); pub struct Name(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, Get)]
pub struct VersionNumber(#[get(method = "get", kind = "deref")] String); pub struct VersionNumber(#[get(method = "get", kind = "deref")] String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] #[derive(Debug, Clone, Get)]
struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec<VersionNumber>); struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec<VersionNumber>);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VersionSuffixKind { pub enum VersionSuffixKind {
Alpha, Alpha,
Beta, Beta,
@@ -49,75 +49,58 @@ pub enum VersionSuffixKind {
P, P,
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq, Get)] #[derive(Clone, Debug, Get)]
pub struct VersionSuffix { pub struct VersionSuffix {
kind: VersionSuffixKind, kind: VersionSuffixKind,
number: Option<VersionNumber>, number: Option<VersionNumber>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] #[derive(Debug, Clone, Get)]
pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec<VersionSuffix>); pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec<VersionSuffix>);
#[derive(Debug, Clone, Get, PartialEq, Eq, Hash)] #[derive(Clone, Debug, Get)]
pub struct BuildId(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)]
pub struct Version { pub struct Version {
numbers: VersionNumbers, numbers: VersionNumbers,
letter: Option<char>, letter: Option<char>,
suffixes: VersionSuffixes, suffixes: VersionSuffixes,
rev: Option<VersionNumber>, rev: Option<VersionNumber>,
build_id: Option<BuildId>,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Wildcard;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SlotOperator { pub enum SlotOperator {
Eq, Eq,
Star, Star,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct SlotName(#[get(method = "name", kind = "deref")] String); pub struct SlotName(#[get(method = "name", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub enum Slot { pub struct Slot {
Wildcard, slot: Option<SlotName>,
Equal, sub: Option<SlotName>,
NameEqual { operator: Option<SlotOperator>,
primary: SlotName,
sub: Option<SlotName>,
},
Name {
primary: SlotName,
sub: Option<SlotName>,
},
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepNegate { pub enum UseDepNegate {
Minus, Minus,
Exclamation, Exclamation,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepSign { pub enum UseDepSign {
Enabled, Enabled,
Disabled, Disabled,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepCondition { pub enum UseDepCondition {
Eq, Eq,
Question, Question,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Repo(String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)]
pub struct UseDep { pub struct UseDep {
negate: Option<UseDepNegate>, negate: Option<UseDepNegate>,
flag: UseFlag, flag: UseFlag,
@@ -125,13 +108,13 @@ pub struct UseDep {
condition: Option<UseDepCondition>, condition: Option<UseDepCondition>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Cp { pub struct Cp {
category: Category, category: Category,
name: Name, name: Name,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Cpv { pub struct Cpv {
category: Category, category: Category,
name: Name, name: Name,
@@ -139,93 +122,41 @@ pub struct Cpv {
slot: Option<Slot>, slot: Option<Slot>,
} }
#[derive(Clone, Debug, Get, PartialEq, Eq, Hash)] #[derive(Clone, Debug, Get, PartialEq, Eq)]
pub struct Atom { pub struct Atom {
blocker: Option<Blocker>, blocker: Option<Blocker>,
category: Category, category: Category,
name: Name, name: Name,
version: Option<(VersionOperator, Version, Option<Wildcard>)>, version: Option<(VersionOperator, Version)>,
slot: Option<Slot>, slot: Option<Slot>,
repo: Option<Repo>,
#[get(kind = "deref")] #[get(kind = "deref")]
usedeps: Vec<UseDep>, usedeps: Vec<UseDep>,
} }
impl Cpv {
#[must_use]
pub fn into_cp(self) -> Cp {
Cp {
name: self.name,
category: self.category,
}
}
}
impl Atom { impl Atom {
#[must_use]
pub fn version_operator(&self) -> Option<VersionOperator> { pub fn version_operator(&self) -> Option<VersionOperator> {
self.version.clone().map(|(oper, _, _)| oper)
}
#[must_use]
pub fn into_cp(self) -> Cp {
Cp {
category: self.category,
name: self.name,
}
}
#[must_use]
pub fn into_cpv(self) -> Option<Cpv> {
match self.version { match self.version {
Some((_, version, _)) => Some(Cpv { Some((operator, _)) => Some(operator),
category: self.category,
name: self.name,
version,
slot: self.slot,
}),
None => None, None => None,
} }
} }
} }
impl VersionNumber { impl PartialEq for VersionSuffix {
#[must_use] fn eq(&self, other: &Self) -> bool {
pub fn cmp_as_ints(&self, other: &Self) -> Ordering { self.kind == other.kind
let a = self.get().trim_start_matches('0'); && match dbg!((&self.number, &other.number)) {
let b = other.get().trim_start_matches('0'); (Some(a), Some(b)) => a.0 == b.0,
(Some(a), None) if a.get().chars().all(|c| c == '0') => true,
a.len().cmp(&b.len()).then_with(|| a.cmp(b)) (None, Some(b)) if b.get().chars().all(|c| c == '0') => true,
} (Some(_), None) => false,
(None, Some(_)) => false,
#[must_use] (None, None) => true,
pub fn cmp_as_str(&self, other: &Self) -> Ordering { }
if self.get().starts_with('0') || other.get().starts_with('0') {
let a = self.get().trim_end_matches('0');
let b = other.get().trim_end_matches('0');
a.cmp(b)
} else {
self.cmp_as_ints(other)
}
} }
} }
impl PartialOrd for BuildId { impl Eq for VersionSuffix {}
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BuildId {
fn cmp(&self, other: &Self) -> Ordering {
// build-id may not start with a zero so we dont need to strip them
self.get()
.len()
.cmp(&other.get().len())
.then_with(|| self.get().cmp(other.get()))
}
}
impl PartialOrd for VersionSuffix { impl PartialOrd for VersionSuffix {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
@@ -239,7 +170,11 @@ impl Ord for VersionSuffix {
Ordering::Less => Ordering::Less, Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater, Ordering::Greater => Ordering::Greater,
Ordering::Equal => match (&self.number, &other.number) { Ordering::Equal => match (&self.number, &other.number) {
(Some(a), Some(b)) => a.cmp_as_ints(b), (Some(a), Some(b)) => {
a.0.parse::<u64>()
.unwrap()
.cmp(&b.0.parse::<u64>().unwrap())
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal, (Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal, (None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal,
(Some(_), None) => Ordering::Greater, (Some(_), None) => Ordering::Greater,
@@ -250,6 +185,24 @@ impl Ord for VersionSuffix {
} }
} }
impl PartialEq for VersionSuffixes {
fn eq(&self, other: &Self) -> bool {
let mut a = self.get().iter();
let mut b = other.get().iter();
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a == b => continue,
(Some(_), Some(_)) => break false,
(None, None) => break true,
_ => break false,
}
}
}
}
impl Eq for VersionSuffixes {}
impl PartialOrd for VersionSuffixes { impl PartialOrd for VersionSuffixes {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@@ -262,11 +215,11 @@ impl Ord for VersionSuffixes {
let mut b = other.get().iter(); let mut b = other.get().iter();
loop { loop {
match (a.next(), b.next()) { match dbg!((a.next(), b.next())) {
(Some(a), Some(b)) => match a.cmp(b) { (Some(a), Some(b)) => match a.cmp(b) {
Ordering::Less => break Ordering::Less, Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater, Ordering::Greater => break Ordering::Greater,
Ordering::Equal => (), Ordering::Equal => continue,
}, },
(Some(a), None) if matches!(a.kind, VersionSuffixKind::P) => { (Some(a), None) if matches!(a.kind, VersionSuffixKind::P) => {
break Ordering::Greater; break Ordering::Greater;
@@ -280,6 +233,41 @@ impl Ord for VersionSuffixes {
} }
} }
impl PartialEq for VersionNumbers {
fn eq(&self, other: &Self) -> bool {
self.get().first().unwrap().get().parse::<u64>().unwrap()
== other.get().first().unwrap().get().parse().unwrap()
&& {
let mut a = self.get().iter().skip(1);
let mut b = other.get().iter().skip(1);
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a.get().starts_with("0") => {
let a = a.get().trim_end_matches("0");
let b = b.get().trim_end_matches("0");
if a != b {
break false;
}
}
(Some(a), Some(b)) => {
if a.get().parse::<u64>().unwrap() != b.get().parse::<u64>().unwrap() {
break false;
}
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => continue,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => continue,
(None, None) => break true,
_ => break false,
}
}
}
}
}
impl Eq for VersionNumbers {}
impl PartialOrd for VersionNumbers { impl PartialOrd for VersionNumbers {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@@ -292,22 +280,41 @@ impl Ord for VersionNumbers {
.get() .get()
.first() .first()
.unwrap() .unwrap()
.cmp_as_ints(other.get().first().unwrap()) .get()
.parse::<u64>()
.unwrap()
.cmp(&other.get().first().unwrap().get().parse::<u64>().unwrap())
{ {
Ordering::Less => Ordering::Less, Ordering::Less => return Ordering::Less,
Ordering::Greater => Ordering::Greater, Ordering::Greater => return Ordering::Greater,
Ordering::Equal => { Ordering::Equal => {
let mut a = self.get().iter().skip(1); let mut a = self.get().iter().skip(1);
let mut b = other.get().iter().skip(1); let mut b = other.get().iter().skip(1);
loop { loop {
match (a.next(), b.next()) { match (a.next(), b.next()) {
(Some(a), Some(b)) => match a.cmp_as_str(b) { (Some(a), Some(b)) if a.get().starts_with("0") => {
let a = a.get().trim_end_matches("0");
let b = b.get().trim_end_matches("0");
match a.cmp(b) {
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => continue,
}
}
(Some(a), Some(b)) => match a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse::<u64>().unwrap())
{
Ordering::Less => break Ordering::Less, Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater, Ordering::Greater => break Ordering::Greater,
Ordering::Equal => (), Ordering::Equal => continue,
}, },
(Some(a), None) if a.get().chars().all(|c| c == '0') => continue,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => continue,
(Some(_), None) => break Ordering::Greater, (Some(_), None) => break Ordering::Greater,
(None, Some(_)) => break Ordering::Less, (None, Some(_)) => break Ordering::Less,
(None, None) => break Ordering::Equal, (None, None) => break Ordering::Equal,
@@ -318,6 +325,25 @@ impl Ord for VersionNumbers {
} }
} }
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.numbers == other.numbers
&& self.suffixes == other.suffixes
&& match (&self.rev, &other.rev) {
(Some(a), Some(b)) => {
a.get().parse::<u64>().unwrap() == b.get().parse::<u64>().unwrap()
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => true,
(Some(_), None) => false,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => true,
(None, Some(_)) => false,
(None, None) => true,
}
}
}
impl Eq for Version {}
impl PartialOrd for Version { impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@@ -327,45 +353,24 @@ impl PartialOrd for Version {
impl Ord for Version { impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
match self.numbers.cmp(&other.numbers) { match self.numbers.cmp(&other.numbers) {
Ordering::Less => return Ordering::Less, Ordering::Less => Ordering::Less,
Ordering::Greater => return Ordering::Greater, Ordering::Greater => Ordering::Greater,
Ordering::Equal => (), Ordering::Equal => match self.suffixes.cmp(&other.suffixes) {
} Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
match (self.letter, other.letter) { Ordering::Equal => match (&self.rev, &other.rev) {
(Some(a), Some(b)) if a < b => return Ordering::Less, (Some(a), Some(b)) => a
(Some(a), Some(b)) if a > b => return Ordering::Greater, .get()
(Some(a), Some(b)) if a == b => (), .parse::<u64>()
(Some(_), None) => return Ordering::Greater, .unwrap()
(None, Some(_)) => return Ordering::Less, .cmp(&b.get().parse().unwrap()),
(None, None) => (), (Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal,
_ => unreachable!(), (Some(_), None) => Ordering::Greater,
} (None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
match self.suffixes.cmp(&other.suffixes) { (None, None) => Ordering::Equal,
Ordering::Less => return Ordering::Less, },
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => (),
}
match (&self.rev, &other.rev) {
(Some(a), Some(b)) => match a.cmp_as_ints(b) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => (),
}, },
(Some(a), None) if a.get().chars().all(|c| c == '0') => (),
(Some(_), None) => return Ordering::Greater,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => (),
(None, Some(_)) => return Ordering::Less,
(None, None) => (),
}
match (&self.build_id, &other.build_id) {
(Some(a), Some(b)) => a.cmp(b),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
} }
} }
} }
@@ -420,12 +425,6 @@ impl fmt::Display for VersionNumber {
} }
} }
impl fmt::Display for BuildId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get())
}
}
impl fmt::Display for VersionSuffixKind { impl fmt::Display for VersionSuffixKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
@@ -456,7 +455,7 @@ impl fmt::Display for Version {
.numbers .numbers
.get() .get()
.iter() .iter()
.map(VersionNumber::get) .map(|n| n.get())
.intersperse(".") .intersperse(".")
.collect::<String>(); .collect::<String>();
@@ -464,28 +463,24 @@ impl fmt::Display for Version {
.suffixes .suffixes
.get() .get()
.iter() .iter()
.map(VersionSuffix::to_string) .map(|s| s.to_string())
.intersperse("_".to_string()) .intersperse("_".to_string())
.collect::<String>(); .collect::<String>();
write!(f, "{numbers}")?; write!(f, "{}", numbers)?;
if let Some(letter) = self.letter { if let Some(letter) = self.letter {
write!(f, "{letter}")?; write!(f, "{letter}")?;
} }
if !suffixes.is_empty() { if suffixes.len() > 0 {
write!(f, "_{suffixes}")?; write!(f, "_{}", suffixes)?;
} }
if let Some(rev) = self.rev.as_ref() { if let Some(rev) = self.rev.as_ref() {
write!(f, "-r{rev}")?; write!(f, "-r{rev}")?;
} }
if let Some(build_id) = self.build_id.as_ref() {
write!(f, "-{build_id}")?;
}
Ok(()) Ok(())
} }
} }
@@ -507,30 +502,19 @@ impl fmt::Display for SlotName {
impl fmt::Display for Slot { impl fmt::Display for Slot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { if let Some(slot) = self.slot.as_ref() {
Self::Wildcard => write!(f, "*"), write!(f, "{slot}")?;
Self::Equal => {
write!(f, "=")
}
Self::NameEqual { primary, sub } => {
write!(f, "{primary}")?;
if let Some(sub) = sub {
write!(f, "/{sub}")?;
}
write!(f, "=")
}
Self::Name { primary, sub } => {
write!(f, "{primary}")?;
if let Some(sub) = sub {
write!(f, "/{sub}")?;
}
Ok(())
}
} }
if let Some(sub) = self.sub.as_ref() {
write!(f, "/{sub}")?;
}
if let Some(operator) = self.operator.as_ref() {
write!(f, "{operator}")?;
}
Ok(())
} }
} }
@@ -613,10 +597,8 @@ impl fmt::Display for Atom {
write!(f, "/")?; write!(f, "/")?;
write!(f, "{}", self.name)?; write!(f, "{}", self.name)?;
if let Some((_, version, None)) = self.version() { if let Some((_, version)) = self.version.as_ref() {
write!(f, "-{version}")?; write!(f, "-{version}")?;
} else if let Some((_, version, Some(_))) = self.version() {
write!(f, "-{version}*")?;
} }
if let Some(slot) = self.slot.as_ref() { if let Some(slot) = self.slot.as_ref() {
@@ -626,7 +608,7 @@ impl fmt::Display for Atom {
let usedeps = self let usedeps = self
.usedeps .usedeps
.iter() .iter()
.map(UseDep::to_string) .map(|u| u.to_string())
.intersperse(",".to_string()) .intersperse(",".to_string())
.collect::<String>(); .collect::<String>();
@@ -686,6 +668,38 @@ mod test {
assert_eq!(atom.to_string().as_str(), s); assert_eq!(atom.to_string().as_str(), s);
} }
#[test]
fn test_version_suffix_eq() {
let a = VersionSuffix::parser()
.parse_finished(InputIter::new("alpha0"))
.unwrap();
let b = VersionSuffix::parser()
.parse_finished(InputIter::new("alpha"))
.unwrap();
assert_eq_display!(a, b);
}
#[test]
fn test_version_eq() {
let versions = [
("1", "1"),
("1", "1.0.0"),
("1.0", "1.0.0"),
("1.0.0_alpha0", "1.0.0_alpha"),
("1.0.0", "1.0.0-r0"),
];
for (a, b) in versions.map(|(a, b)| {
(
Version::parser().parse_finished(InputIter::new(a)).unwrap(),
Version::parser().parse_finished(InputIter::new(b)).unwrap(),
)
}) {
assert_eq_display!(a, b);
}
}
#[test] #[test]
fn test_version_cmp() { fn test_version_cmp() {
let versions = [ let versions = [
@@ -694,7 +708,6 @@ mod test {
("1.0.0_alpha", "1.0.0_alpha_p", Ordering::Less), ("1.0.0_alpha", "1.0.0_alpha_p", Ordering::Less),
("1.0.0-r0", "1.0.0", Ordering::Equal), ("1.0.0-r0", "1.0.0", Ordering::Equal),
("1.0.0-r0000", "1.0.0", Ordering::Equal), ("1.0.0-r0000", "1.0.0", Ordering::Equal),
("1.0.0-r1-1", "1.0.0-r1-2", Ordering::Less),
]; ];
for (a, b, ordering) in versions.iter().map(|(a, b, ordering)| { for (a, b, ordering) in versions.iter().map(|(a, b, ordering)| {
@@ -725,55 +738,4 @@ mod test {
assert_partial_cmp_display!(a, b, ordering); assert_partial_cmp_display!(a, b, ordering);
} }
} }
#[test]
fn test_version_cmp_letter() {
let a = Version::parser()
.parse_finished(InputIter::new("1.0.0"))
.unwrap();
let b = Version::parser()
.parse_finished(InputIter::new("1.0.0a"))
.unwrap();
assert_cmp_display!(a, b, Ordering::Less);
}
#[test]
fn test_version_cmp_where_b_has_leading_zeros() {
let a = Version::parser()
.parse_finished(InputIter::new("1.2"))
.unwrap();
let b = Version::parser()
.parse_finished(InputIter::new("1.054"))
.unwrap();
assert_cmp_display!(a, b, Ordering::Greater);
}
#[test]
fn test_version_has_more_zeros() {
let a = Version::parser()
.parse_finished(InputIter::new("1.0.0"))
.unwrap();
let b = Version::parser()
.parse_finished(InputIter::new("1.0"))
.unwrap();
assert_cmp_display!(a, b, Ordering::Greater);
}
#[test]
fn test_fuzzer_cases() {
let control = Version::parser()
.parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1-8"))
.unwrap();
for (version_str, expected) in [("1.2.0", Ordering::Greater)] {
let version = Version::parser()
.parse_finished(InputIter::new(version_str))
.unwrap();
assert_cmp_display!(control, version, expected);
}
}
} }

View File

@@ -1,16 +1,13 @@
use core::option::Option::None; use core::option::Option::None;
use mon::{ use mon::{Parser, ParserIter, alphanumeric, r#if, numeric1, one_of, tag};
Parser, ParserIter, ascii_alphanumeric, ascii_numeric, ascii_numeric1, eof, r#if,
input::InputIter, one_of, tag,
};
use crate::{ use crate::{
Parseable, Parseable,
atom::{ atom::{
Atom, Blocker, BuildId, Category, Cp, Cpv, Name, Repo, Slot, SlotName, SlotOperator, Atom, Blocker, Category, Cp, Cpv, Name, Slot, SlotName, SlotOperator, UseDep,
UseDep, UseDepCondition, UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, UseDepCondition, UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers,
VersionOperator, VersionSuffix, VersionSuffixKind, VersionSuffixes, Wildcard, VersionOperator, VersionSuffix, VersionSuffixKind, VersionSuffixes,
}, },
useflag::UseFlag, useflag::UseFlag,
}; };
@@ -43,22 +40,7 @@ impl<'a> Parseable<'a, &'a str> for VersionNumber {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
ascii_numeric1().map(|output: &str| VersionNumber(output.to_string())) numeric1().map(|output: &str| VersionNumber(output.to_string()))
}
}
impl<'a> Parseable<'a, &'a str> for BuildId {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
let start = ascii_numeric().and_not(tag("0"));
let rest = ascii_numeric().repeated().many();
start
.and(rest)
.recognize()
.or(tag("0"))
.map(|output: &str| BuildId(output.to_string()))
} }
} }
@@ -90,9 +72,12 @@ impl<'a> Parseable<'a, &'a str> for VersionNumbers {
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
VersionNumber::parser() VersionNumber::parser()
.followed_by(tag("*").opt())
.recognize()
.map(|output: &str| VersionNumber(output.to_string()))
.separated_by(tag(".")) .separated_by(tag("."))
.at_least(1) .at_least(1)
.map(VersionNumbers) .map(|numbers| VersionNumbers(numbers))
} }
} }
@@ -103,7 +88,7 @@ impl<'a> Parseable<'a, &'a str> for VersionSuffixes {
VersionSuffix::parser() VersionSuffix::parser()
.separated_by(tag("_")) .separated_by(tag("_"))
.at_least(1) .at_least(1)
.map(VersionSuffixes) .map(|suffixes| VersionSuffixes(suffixes))
} }
} }
@@ -112,19 +97,16 @@ impl<'a> Parseable<'a, &'a str> for Version {
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let rev = VersionNumber::parser().preceded_by(tag("-r")); let rev = VersionNumber::parser().preceded_by(tag("-r"));
let build_id = BuildId::parser().preceded_by(tag("-"));
VersionNumbers::parser() VersionNumbers::parser()
.and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt()) .and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt())
.and(VersionSuffixes::parser().preceded_by(tag("_")).opt()) .and(VersionSuffixes::parser().preceded_by(tag("_")).opt())
.and(rev.opt()) .and(rev.opt())
.and(build_id.opt()) .map(|(((numbers, letter), suffixes), rev)| Version {
.map(|((((numbers, letter), suffixes), rev), build_id)| Version {
numbers, numbers,
letter, letter,
suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())), suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())),
rev, rev,
build_id,
}) })
} }
} }
@@ -133,11 +115,8 @@ impl<'a> Parseable<'a, &'a str> for Category {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = ascii_alphanumeric().or(one_of("_".chars())); let start = alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric() let rest = alphanumeric().or(one_of("+_.-".chars())).repeated().many();
.or(one_of("+_.-".chars()))
.repeated()
.many();
start start
.and(rest) .and(rest)
@@ -150,31 +129,21 @@ impl<'a> Parseable<'a, &'a str> for Name {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = || ascii_alphanumeric().or(one_of("_".chars())); let start = alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric() let rest = alphanumeric()
.or(one_of("_+".chars())) .or(one_of("_+".chars()))
.or(one_of("-".chars()).and_not( .or(one_of("-".chars()).and_not(
Version::parser() Version::parser()
.preceded_by(tag("-")) .preceded_by(tag("-"))
.followed_by(ascii_alphanumeric().or(one_of("_+-".chars())).not()), .followed_by(alphanumeric().or(one_of("_+-".chars())).not()),
)) ))
.repeated() .repeated()
.many(); .many();
let verify = ascii_alphanumeric() start
.or(one_of("_+".chars()))
.or(one_of("-".chars())
.and_not(Version::parser().preceded_by(tag("-")).followed_by(eof())))
.repeated()
.many();
start()
.and(rest) .and(rest)
.recognize() .recognize()
.verify_output(move |output: &&str| {
verify.check_finished(InputIter::new(*output)).is_ok()
})
.map(|output: &str| Name(output.to_string())) .map(|output: &str| Name(output.to_string()))
} }
} }
@@ -193,11 +162,8 @@ impl<'a> Parseable<'a, &'a str> for SlotName {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = ascii_alphanumeric().or(one_of("_".chars())); let start = alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric() let rest = alphanumeric().or(one_of("+_.-".chars())).repeated().many();
.or(one_of("+_.-".chars()))
.repeated()
.many();
start start
.and(rest) .and(rest)
@@ -210,17 +176,15 @@ impl<'a> Parseable<'a, &'a str> for Slot {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let wildcard = tag("*").map(|_| Slot::Wildcard); SlotName::parser()
let equal = tag("=").map(|_| Slot::Equal); .opt()
let name_equal = SlotName::parser()
.and(SlotName::parser().preceded_by(tag("/")).opt()) .and(SlotName::parser().preceded_by(tag("/")).opt())
.followed_by(tag("=")) .and(SlotOperator::parser().opt())
.map(|(primary, sub)| Slot::NameEqual { primary, sub }); .map(|((slot, sub), operator)| Slot {
let name = SlotName::parser() slot,
.and(SlotName::parser().preceded_by(tag("/")).opt()) sub,
.map(|(primary, sub)| Self::Name { primary, sub }); operator,
})
wildcard.or(equal).or(name_equal).or(name)
} }
} }
@@ -234,32 +198,9 @@ impl<'a> Parseable<'a, &'a str> for UseDepSign {
} }
} }
impl<'a> Parseable<'a, &'a str> for Repo {
type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser {
let start = ascii_alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric()
.or(one_of("_-".chars()))
.repeated()
.many();
start
.and(rest)
.recognize()
.verify_output(move |output: &&str| {
Name::parser()
.check_finished(InputIter::new(*output))
.is_ok()
})
.map(|output: &str| Repo(output.to_string()))
}
}
impl<'a> Parseable<'a, &'a str> for UseDep { impl<'a> Parseable<'a, &'a str> for UseDep {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
#[allow(clippy::many_single_char_names)]
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let a = UseFlag::parser() let a = UseFlag::parser()
.and(UseDepSign::parser().opt()) .and(UseDepSign::parser().opt())
@@ -333,7 +274,7 @@ impl<'a> Parseable<'a, &'a str> for Atom {
let usedeps = || { let usedeps = || {
UseDep::parser() UseDep::parser()
.separated_by(tag(",")) .separated_by(tag(","))
.at_least(1) .many()
.delimited_by(tag("["), tag("]")) .delimited_by(tag("["), tag("]"))
.opt() .opt()
}; };
@@ -343,19 +284,15 @@ impl<'a> Parseable<'a, &'a str> for Atom {
.and(Category::parser()) .and(Category::parser())
.and(Name::parser().preceded_by(tag("/"))) .and(Name::parser().preceded_by(tag("/")))
.and(Slot::parser().preceded_by(tag(":")).opt()) .and(Slot::parser().preceded_by(tag(":")).opt())
.and(Repo::parser().preceded_by(tag("::")).opt())
.and(usedeps()) .and(usedeps())
.map( .map(|((((blocker, category), name), slot), usedeps)| Atom {
|(((((blocker, category), name), slot), repo), usedeps)| Atom { blocker,
blocker, category,
category, name,
name, version: None,
version: None, slot,
slot, usedeps: usedeps.unwrap_or(Vec::new()),
repo, });
usedeps: usedeps.unwrap_or(Vec::new()),
},
);
let with_version = Blocker::parser() let with_version = Blocker::parser()
.opt() .opt()
@@ -363,37 +300,33 @@ impl<'a> Parseable<'a, &'a str> for Atom {
.and(Category::parser()) .and(Category::parser())
.and(Name::parser().preceded_by(tag("/"))) .and(Name::parser().preceded_by(tag("/")))
.and(Version::parser().preceded_by(tag("-"))) .and(Version::parser().preceded_by(tag("-")))
.and(tag("*").map(|_| Wildcard).opt())
.and(Slot::parser().preceded_by(tag(":")).opt()) .and(Slot::parser().preceded_by(tag(":")).opt())
.and(Repo::parser().preceded_by(tag("::")).opt())
.and(usedeps()) .and(usedeps())
.verify_output(
|((((((((_, version_operator), _), _), version), star), _), _), _)| {
matches!(
(version_operator, star),
(VersionOperator::Eq, Some(_) | None) | (_, None)
) && matches!((version.build_id(), star), (Some(_), None) | (None, _))
},
)
.map( .map(
|( |((((((blocker, version_operator), category), name), version), slot), usedeps)| {
(
((((((blocker, version_operator), category), name), version), star), slot),
repo,
),
usedeps,
)| {
Atom { Atom {
blocker, blocker,
category, category,
name, name,
version: Some((version_operator, version, star)), version: Some((version_operator, version)),
slot, slot,
repo,
usedeps: usedeps.unwrap_or(Vec::new()), usedeps: usedeps.unwrap_or(Vec::new()),
} }
}, },
); )
.verify_output(|atom| match &atom.version {
Some((VersionOperator::Eq, _)) => true,
Some((_, version))
if !version
.numbers()
.get()
.iter()
.any(|number| number.get().contains("*")) =>
{
true
}
_ => false,
});
with_version.or(without_version) with_version.or(without_version)
} }
@@ -481,14 +414,14 @@ mod test {
fn test_invalid_usedep() { fn test_invalid_usedep() {
let it = InputIter::new("foo-bar:slot/sub=[!use]"); let it = InputIter::new("foo-bar:slot/sub=[!use]");
assert!(Atom::parser().check_finished(it).is_err()); assert!(Atom::parser().check_finished(it).is_err())
} }
#[test] #[test]
fn test_empty_slot() { fn test_empty_slot() {
let it = InputIter::new("=dev-ml/uucp-17*:"); let it = InputIter::new("foo/bar:=");
assert!(Atom::parser().check_finished(it).is_err()); Atom::parser().check_finished(it).unwrap();
} }
#[test] #[test]
@@ -560,40 +493,4 @@ mod test {
assert!(Cpv::parser().parse_finished(it).is_err()); assert!(Cpv::parser().parse_finished(it).is_err());
} }
#[test]
fn test_empty_slot_with_operator() {
let it = InputIter::new("foo/bar:=");
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_with_repo() {
let it = InputIter::new("=foo/bar-1.0.0:slot/sub=::gentoo[a,b,c]");
Atom::parser().check_finished(it).unwrap();
}
#[test]
fn test_against_fuzzer_false_positives() {
let atoms = [
"media-libs/libsdl2[haptitick(+),sound(+)vd,eio(+)]",
"=kde-frameworks/kcodecs-6.19*86",
"=dev-ml/stdio-0.17*t:=[ocamlopt?]",
">=dev-libs/libgee-0-8.5:0..8=",
"<dev-haskell/wai-3.3:=[]",
">=kde-frameworks/kcrash-2.16.0:6*",
"0-f/merreka+m::k+",
"iev-a/h:/n=",
"=dev-ml/stdio-0-17*:=[ocamlopt?]",
];
for atom in atoms {
assert!(
Atom::parser().check_finished(InputIter::new(atom)).is_err(),
"{atom}"
);
}
}
} }

View File

@@ -6,7 +6,8 @@ use crate::{
useflag::{IUseFlag, UseFlag}, useflag::{IUseFlag, UseFlag},
}; };
mod parsers; pub mod parsers;
pub mod repo;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Conditional { pub enum Conditional {
@@ -58,26 +59,26 @@ pub struct Eclass(#[get(method = "get", kind = "deref")] String);
#[derive(Debug, Clone, Get)] #[derive(Debug, Clone, Get)]
pub struct Ebuild { pub struct Ebuild {
pub(super) name: Name, name: Name,
pub(super) version: Version, version: Version,
pub(super) slot: Option<Slot>, slot: Option<Slot>,
pub(super) homepage: Option<String>, homepage: Option<String>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) src_uri: Vec<Depend<SrcUri>>, src_uri: Vec<Depend<SrcUri>>,
pub(super) eapi: Option<Eapi>, eapi: Option<Eapi>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) inherit: Vec<Eclass>, inherit: Vec<Eclass>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) iuse: Vec<IUseFlag>, iuse: Vec<IUseFlag>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) license: Vec<Depend<License>>, license: Vec<Depend<License>>,
pub(super) description: Option<String>, description: Option<String>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) depend: Vec<Depend<Atom>>, depend: Vec<Depend<Atom>>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) bdepend: Vec<Depend<Atom>>, bdepend: Vec<Depend<Atom>>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) rdepend: Vec<Depend<Atom>>, rdepend: Vec<Depend<Atom>>,
#[get(kind = "deref")] #[get(kind = "deref")]
pub(super) idepend: Vec<Depend<Atom>>, idepend: Vec<Depend<Atom>>,
} }

View File

@@ -1,12 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use mon::{ use mon::{Parser, ParserIter, alpha1, alphanumeric, r#if, one_of, tag, whitespace1};
Parser, ParserIter, ascii_alpha1, ascii_alphanumeric, ascii_whitespace1, r#if, one_of, tag,
};
use crate::{ use crate::{
Parseable, Parseable,
repo::ebuild::{Conditional, Depend, Eapi, Eclass, License, SrcUri, Uri, UriPrefix}, ebuild::{Conditional, Depend, Eapi, Eclass, License, SrcUri, Uri, UriPrefix},
useflag::UseFlag, useflag::UseFlag,
}; };
@@ -24,7 +22,7 @@ impl<'a> Parseable<'a, &'a str> for Uri {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let protocol = ascii_alpha1::<&str>() let protocol = alpha1::<&str>()
.followed_by(tag("://")) .followed_by(tag("://"))
.map(|output: &str| output.to_string()); .map(|output: &str| output.to_string());
let path = r#if(|c: &char| !c.is_ascii_whitespace()) let path = r#if(|c: &char| !c.is_ascii_whitespace())
@@ -69,11 +67,8 @@ impl<'a> Parseable<'a, &'a str> for License {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = ascii_alphanumeric().or(one_of("_".chars())); let start = alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric() let rest = alphanumeric().or(one_of("+_.-".chars())).repeated().many();
.or(one_of("+_.-".chars()))
.repeated()
.many();
start start
.and(rest) .and(rest)
@@ -86,11 +81,8 @@ impl<'a> Parseable<'a, &'a str> for Eapi {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = ascii_alphanumeric().or(one_of("_".chars())); let start = alphanumeric().or(one_of("_".chars()));
let rest = ascii_alphanumeric() let rest = alphanumeric().or(one_of("+_.-".chars())).repeated().many();
.or(one_of("+_.-".chars()))
.repeated()
.many();
start start
.and(rest) .and(rest)
@@ -122,26 +114,46 @@ where
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
|it| { |it| {
let exprs = || { let all_of_group = Depend::parser()
Depend::parser() .separated_by(whitespace1())
.separated_by_with_trailing(ascii_whitespace1()) .at_least(1)
.at_least(1) .delimited_by(
.delimited_by(tag("(").followed_by(ascii_whitespace1()), tag(")")) tag("(").followed_by(whitespace1()),
}; tag(")").preceded_by(whitespace1().opt()),
)
.map(|exprs| Depend::AllOf(exprs));
let all_of_group = exprs().map(|exprs| Depend::AllOf(exprs)); let any_of_group = Depend::parser()
.separated_by(whitespace1())
let any_of_group = exprs() .at_least(1)
.preceded_by(tag("||").followed_by(ascii_whitespace1())) .delimited_by(
tag("(").followed_by(whitespace1()),
tag(")").preceded_by(whitespace1().opt()),
)
.preceded_by(tag("||").followed_by(whitespace1()))
.map(|exprs| Depend::AnyOf(exprs)); .map(|exprs| Depend::AnyOf(exprs));
let one_of_group = exprs() let one_of_group = Depend::parser()
.preceded_by(tag("^^").followed_by(ascii_whitespace1())) .separated_by(whitespace1())
.at_least(1)
.delimited_by(
tag("(").followed_by(whitespace1()),
tag(")").preceded_by(whitespace1().opt()),
)
.preceded_by(tag("^^").followed_by(whitespace1()))
.map(|exprs| Depend::OneOf(exprs)); .map(|exprs| Depend::OneOf(exprs));
let conditional_group = Conditional::parser() let conditional_group = Conditional::parser()
.followed_by(ascii_whitespace1()) .followed_by(whitespace1())
.and(exprs()) .and(
Depend::parser()
.separated_by(whitespace1())
.at_least(1)
.delimited_by(
tag("(").followed_by(whitespace1()),
tag(")").preceded_by(whitespace1().opt()),
),
)
.map(|(conditional, exprs)| Depend::ConditionalGroup(conditional, exprs)); .map(|(conditional, exprs)| Depend::ConditionalGroup(conditional, exprs));
T::parser() T::parser()
@@ -162,10 +174,10 @@ impl<'a> Parseable<'a, &'a str> for Conditional {
UseFlag::parser() UseFlag::parser()
.preceded_by(tag("!")) .preceded_by(tag("!"))
.followed_by(tag("?")) .followed_by(tag("?"))
.map(Conditional::Negative) .map(|flag| Conditional::Negative(flag))
.or(UseFlag::parser() .or(UseFlag::parser()
.followed_by(tag("?")) .followed_by(tag("?"))
.map(Conditional::Positive)) .map(|flag| Conditional::Positive(flag)))
} }
} }
@@ -174,7 +186,7 @@ mod test {
use mon::{ParserIter, input::InputIter}; use mon::{ParserIter, input::InputIter};
use crate::{atom::Atom, repo::ebuild::Depend}; use crate::{atom::Atom, ebuild::Depend};
use super::*; use super::*;
@@ -188,7 +200,7 @@ mod test {
for test in tests { for test in tests {
SrcUri::parser() SrcUri::parser()
.check_finished(InputIter::new(test)) .check_finished(InputIter::new(test))
.unwrap(); .unwrap()
} }
} }
@@ -197,7 +209,7 @@ mod test {
let it = InputIter::new("flag? ( || ( foo/bar foo/bar ) )"); let it = InputIter::new("flag? ( || ( foo/bar foo/bar ) )");
Depend::<Atom>::parser() Depend::<Atom>::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.check_finished(it) .check_finished(it)
.unwrap(); .unwrap();

View File

@@ -5,17 +5,15 @@ use std::{
use get::Get; use get::Get;
use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag}; use mon::{Parser, ParserIter, input::InputIter, tag, whitespace1};
use crate::{ use crate::{
Parseable, Parseable,
atom::{self, Atom}, atom::{self, Atom},
repo::ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri},
useflag::IUseFlag, useflag::IUseFlag,
}; };
pub mod ebuild;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("io error: {0}")] #[error("io error: {0}")]
@@ -201,7 +199,7 @@ fn read_slot(input: &str) -> Option<Result<atom::Slot, Error>> {
fn read_homepage(input: &str) -> Option<String> { fn read_homepage(input: &str) -> Option<String> {
input input
.lines() .lines()
.find_map(|line| line.strip_prefix("HOMEPAGE=").map(str::to_string)) .find_map(|line| line.strip_prefix("HOMEPAGE=").map(|s| s.to_string()))
} }
fn read_src_uri(input: &str) -> Option<Result<Vec<Depend<SrcUri>>, Error>> { fn read_src_uri(input: &str) -> Option<Result<Vec<Depend<SrcUri>>, Error>> {
@@ -210,7 +208,7 @@ fn read_src_uri(input: &str) -> Option<Result<Vec<Depend<SrcUri>>, Error>> {
.find_map(|line| line.strip_prefix("SRC_URI="))?; .find_map(|line| line.strip_prefix("SRC_URI="))?;
match Depend::<SrcUri>::parser() match Depend::<SrcUri>::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.parse_finished(InputIter::new(line)) .parse_finished(InputIter::new(line))
{ {
@@ -234,7 +232,7 @@ fn read_inherit(input: &str) -> Option<Result<Vec<Eclass>, Error>> {
.find_map(|line| line.strip_prefix("INHERIT="))?; .find_map(|line| line.strip_prefix("INHERIT="))?;
match Eclass::parser() match Eclass::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.parse_finished(InputIter::new(line)) .parse_finished(InputIter::new(line))
{ {
@@ -247,7 +245,7 @@ fn read_iuse(input: &str) -> Option<Result<Vec<IUseFlag>, Error>> {
let line = input.lines().find_map(|line| line.strip_prefix("IUSE="))?; let line = input.lines().find_map(|line| line.strip_prefix("IUSE="))?;
match IUseFlag::parser() match IUseFlag::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.parse_finished(InputIter::new(line)) .parse_finished(InputIter::new(line))
{ {
@@ -262,7 +260,7 @@ fn read_license(input: &str) -> Option<Result<Vec<Depend<License>>, Error>> {
.find_map(|line| line.strip_suffix("LICENSE="))?; .find_map(|line| line.strip_suffix("LICENSE="))?;
match Depend::<License>::parser() match Depend::<License>::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.parse_finished(InputIter::new(line)) .parse_finished(InputIter::new(line))
{ {
@@ -274,7 +272,7 @@ fn read_license(input: &str) -> Option<Result<Vec<Depend<License>>, Error>> {
fn read_description(input: &str) -> Option<String> { fn read_description(input: &str) -> Option<String> {
input input
.lines() .lines()
.find_map(|line| line.strip_prefix("DESCRIPTION=").map(str::to_string)) .find_map(|line| line.strip_prefix("DESCRIPTION=").map(|s| s.to_string()))
} }
fn read_depend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> { fn read_depend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
@@ -311,7 +309,7 @@ fn read_idepend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
fn parse_depends(line: &str) -> Result<Vec<Depend<Atom>>, Error> { fn parse_depends(line: &str) -> Result<Vec<Depend<Atom>>, Error> {
Depend::<Atom>::parser() Depend::<Atom>::parser()
.separated_by(ascii_whitespace1()) .separated_by(whitespace1())
.many() .many()
.parse_finished(InputIter::new(line)) .parse_finished(InputIter::new(line))
.map_err(|_| Error::Parser(line.to_string())) .map_err(|_| Error::Parser(line.to_string()))

View File

@@ -1,91 +1,15 @@
//! Gentoo and PMS related utils.
//!
//! Currently implements:
//! - parsers for atoms and DEPEND expressions
//! - strongly typed representations of atoms, versions, etc
//! - version comparison and equality impls
//! - iterator over repos categories and ebuilds
//!
//! Planned features
//! - profile evaluation
//! - vdb reader
//! - sourcing ebuilds with bash
//!
#![deny(clippy::pedantic, unused_imports)] #![deny(clippy::pedantic, unused_imports)]
#![allow( #![allow(dead_code, unstable_name_collisions)]
dead_code,
unstable_name_collisions,
clippy::missing_errors_doc,
clippy::missing_panics_doc
)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
use mon::{ use mon::{Parser, input::Input};
Parser,
input::{Input, InputIter},
};
pub trait Parseable<'a, I: Input + 'a> { pub trait Parseable<'a, I: Input + 'a> {
type Parser: Parser<I, Output = Self>; type Parser: Parser<I, Output = Self>;
fn parser() -> Self::Parser; fn parser() -> Self::Parser;
fn parse(input: I) -> Result<Self, I>
where
Self: Sized,
{
Self::parser()
.parse_finished(InputIter::new(input))
.map_err(|e| e.rest())
}
} }
/// Strongly typed atom and cpv representations.
///
/// Create atoms from parsers:
/// ```
/// use gentoo_utils::{Parseable, atom::Atom};
///
/// let emacs = Atom::parse("=app-editors/emacs-31.0-r1")
/// .expect("failed to parse atom");
///
/// assert_eq!(emacs.to_string(), "=app-editors/emacs-31.0-r1");
/// ````
///
/// Compare versions:
/// ```
/// use gentoo_utils::{Parseable, atom::Cpv};
///
/// let a = Cpv::parse("foo/bar-1.0").unwrap();
/// let b = Cpv::parse("foo/bar-2.0").unwrap();
///
/// assert!(a < b);
/// ```
pub mod atom; pub mod atom;
pub mod ebuild;
/// Access to repos and ebuilds.
///
/// ```
/// use gentoo_utils::repo::Repo;
///
/// let repo = Repo::new("/var/db/repos/gentoo");
///
/// for result in repo.categories().expect("failed to read categories") {
/// let category = result.expect("failed to read category");
///
/// for result in category.ebuilds().expect("failed to read ebuilds") {
/// let ebuild = result.expect("failed to read ebuild");
///
/// println!(
/// "{}-{}: {}",
/// ebuild.name(),
/// ebuild.version(),
/// ebuild.description().clone().unwrap_or("no description available".to_string())
/// );
/// }
/// }
///
/// ```
pub mod repo;
pub mod useflag; pub mod useflag;

View File

@@ -2,12 +2,12 @@ use core::fmt;
use get::Get; use get::Get;
mod parsers; pub mod parsers;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct UseFlag(#[get(method = "name", kind = "deref")] String); pub struct UseFlag(#[get(method = "name", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] #[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct IUseFlag { pub struct IUseFlag {
default: bool, default: bool,
flag: UseFlag, flag: UseFlag,

View File

@@ -1,4 +1,4 @@
use mon::{Parser, ParserIter, ascii_alphanumeric, one_of, tag}; use mon::{Parser, ParserIter, alphanumeric, one_of, tag};
use crate::{ use crate::{
Parseable, Parseable,
@@ -9,11 +9,8 @@ impl<'a> Parseable<'a, &'a str> for UseFlag {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
let start = ascii_alphanumeric(); let start = alphanumeric();
let rest = ascii_alphanumeric() let rest = alphanumeric().or(one_of("+_@-".chars())).repeated().many();
.or(one_of("+_@-".chars()))
.repeated()
.many();
start start
.and(rest) .and(rest)

View File

@@ -1 +0,0 @@
github: dtolnay

View File

@@ -1,128 +0,0 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
schedule: [cron: "40 1 * * *"]
permissions:
contents: read
env:
RUSTFLAGS: -Dwarnings
jobs:
pre_ci:
uses: dtolnay/.github/.github/workflows/pre_ci.yml@master
test:
name: Rust ${{matrix.rust}}
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust: [nightly, beta, stable, 1.81.0, 1.76.0]
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
components: rust-src
- name: Enable type layout randomization
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- name: Enable nightly-only tests
run: echo RUSTFLAGS=${RUSTFLAGS}\ --cfg=thiserror_nightly_testing >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- run: cargo test --workspace --exclude thiserror_no_std_test
- run: cargo test --manifest-path tests/no-std/Cargo.toml
if: matrix.rust != '1.76.0'
- run: cargo test --no-default-features
- uses: actions/upload-artifact@v4
if: matrix.rust == 'nightly' && always()
with:
name: Cargo.lock
path: Cargo.lock
continue-on-error: true
msrv:
name: Rust 1.68.0
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@1.68.0
with:
components: rust-src
- run: cargo check
minimal:
name: Minimal versions
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
- run: cargo generate-lockfile -Z minimal-versions
- run: cargo check --locked
doc:
name: Documentation
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- uses: dtolnay/install@cargo-docs-rs
- run: cargo docs-rs
clippy:
name: Clippy
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy, rust-src
- run: cargo clippy --tests --workspace -- -Dclippy::all -Dclippy::pedantic
miri:
name: Miri
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@miri
- run: cargo miri setup
- run: cargo miri test
env:
MIRIFLAGS: -Zmiri-strict-provenance
outdated:
name: Outdated
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/install@cargo-outdated
- run: cargo outdated --workspace --exit-code 1

View File

@@ -1,2 +0,0 @@
/target/
/Cargo.lock

View File

@@ -1,50 +0,0 @@
[package]
name = "thiserror"
version = "2.0.17"
authors = ["David Tolnay <dtolnay@gmail.com>"]
categories = ["rust-patterns", "no-std"]
description = "derive(Error)"
documentation = "https://docs.rs/thiserror"
edition = "2021"
keywords = ["error", "error-handling", "derive"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.68"
[features]
default = ["std"]
# Std feature enables support for formatting std::path::{Path, PathBuf}
# conveniently in an error message.
#
# #[derive(Error, Debug)]
# #[error("failed to create configuration file {path}")]
# pub struct MyError {
# pub path: PathBuf,
# pub source: std::io::Error,
# }
#
# Without std, this would need to be written #[error("... {}", path.display())].
std = []
[dependencies]
thiserror-impl = { version = "=2.0.17", path = "impl" }
[dev-dependencies]
anyhow = "1.0.73"
ref-cast = "1.0.18"
rustversion = "1.0.13"
trybuild = { version = "1.0.108", features = ["diff"] }
[workspace]
members = ["impl", "tests/no-std"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = [
"--generate-link-to-definition",
"--generate-macro-expansion",
"--extern-html-root-url=core=https://doc.rust-lang.org",
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
"--extern-html-root-url=std=https://doc.rust-lang.org",
]

View File

@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,238 +0,0 @@
derive(Error)
=============
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/thiserror-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/thiserror)
[<img alt="crates.io" src="https://img.shields.io/crates/v/thiserror.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/thiserror)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-thiserror-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/thiserror)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/thiserror/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/thiserror/actions?query=branch%3Amaster)
This library provides a convenient derive macro for the standard library's
[`std::error::Error`] trait.
[`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html
```toml
[dependencies]
thiserror = "2"
```
*Compiler support: requires rustc 1.68+*
<br>
## Example
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
```
<br>
## Details
- Thiserror deliberately does not appear in your public API. You get the same
thing as if you had written an implementation of `std::error::Error` by hand,
and switching from handwritten impls to thiserror or vice versa is not a
breaking change.
- Errors may be enums, structs with named fields, tuple structs, or unit
structs.
- A `Display` impl is generated for your error if you provide `#[error("...")]`
messages on the struct or each variant of your enum, as shown above in the
example.
The messages support a shorthand for interpolating fields from the error.
- `#[error("{var}")]`&ensp;⟶&ensp;`write!("{}", self.var)`
- `#[error("{0}")]`&ensp;⟶&ensp;`write!("{}", self.0)`
- `#[error("{var:?}")]`&ensp;⟶&ensp;`write!("{:?}", self.var)`
- `#[error("{0:?}")]`&ensp;⟶&ensp;`write!("{:?}", self.0)`
These shorthands can be used together with any additional format args, which
may be arbitrary expressions. For example:
```rust
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
InvalidLookahead(u32),
}
```
If one of the additional expression arguments needs to refer to a field of the
struct or enum, then refer to named fields as `.var` and tuple fields as `.0`.
```rust
#[derive(Error, Debug)]
pub enum Error {
#[error("first letter must be lowercase but was {:?}", first_char(.0))]
WrongCase(String),
#[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
OutOfBounds { idx: usize, limits: Limits },
}
```
- A `From` impl is generated for each variant that contains a `#[from]`
attribute.
The variant using `#[from]` must not contain any other fields beyond the
source error (and possibly a backtrace &mdash; see below). Usually `#[from]`
fields are unnamed, but `#[from]` is allowed on a named field too.
```rust
#[derive(Error, Debug)]
pub enum MyError {
Io(#[from] io::Error),
Glob(#[from] globset::Error),
}
```
- The Error trait's `source()` method is implemented to return whichever field
has a `#[source]` attribute or is named `source`, if any. This is for
identifying the underlying lower level error that caused your error.
The `#[from]` attribute always implies that the same field is `#[source]`, so
you don't ever need to specify both attributes.
Any error type that implements `std::error::Error` or dereferences to `dyn
std::error::Error` will work as a source.
```rust
#[derive(Error, Debug)]
pub struct MyError {
msg: String,
#[source] // optional if field name is `source`
source: anyhow::Error,
}
```
- The Error trait's `provide()` method is implemented to provide whichever field
has a type named `Backtrace`, if any, as a `std::backtrace::Backtrace`. Using
`Backtrace` in errors requires a nightly compiler with Rust version 1.73 or
newer.
```rust
use std::backtrace::Backtrace;
#[derive(Error, Debug)]
pub struct MyError {
msg: String,
backtrace: Backtrace, // automatically detected
}
```
- If a field is both a source (named `source`, or has `#[source]` or `#[from]`
attribute) *and* is marked `#[backtrace]`, then the Error trait's `provide()`
method is forwarded to the source's `provide` so that both layers of the error
share the same backtrace. The `#[backtrace]` attribute requires a nightly
compiler with Rust version 1.73 or newer.
```rust
#[derive(Error, Debug)]
pub enum MyError {
Io {
#[backtrace]
source: io::Error,
},
}
```
- For variants that use `#[from]` and also contain a `Backtrace` field, a
backtrace is captured from within the `From` impl.
```rust
#[derive(Error, Debug)]
pub enum MyError {
Io {
#[from]
source: io::Error,
backtrace: Backtrace,
},
}
```
- Errors may use `error(transparent)` to forward the source and Display methods
straight through to an underlying error without adding an additional message.
This would be appropriate for enums that need an "anything else" variant.
```rust
#[derive(Error, Debug)]
pub enum MyError {
...
#[error(transparent)]
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}
```
Another use case is hiding implementation details of an error representation
behind an opaque error type, so that the representation is able to evolve
without breaking the crate's public API.
```rust
// PublicError is public, but opaque and easy to keep compatible.
#[derive(Error, Debug)]
#[error(transparent)]
pub struct PublicError(#[from] ErrorRepr);
impl PublicError {
// Accessors for anything we do want to expose publicly.
}
// Private and free to change across minor version of the crate.
#[derive(Error, Debug)]
enum ErrorRepr {
...
}
```
- See also the [`anyhow`] library for a convenient single error type to use in
application code.
[`anyhow`]: https://github.com/dtolnay/anyhow
<br>
## Comparison to anyhow
Use thiserror if you care about designing your own dedicated error type(s) so
that the caller receives exactly the information that you choose in the event of
failure. This most often applies to library-like code. Use [Anyhow] if you don't
care what error type your functions return, you just want it to be easy. This is
common in application-like code.
[Anyhow]: https://github.com/dtolnay/anyhow
<br>
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@@ -1,33 +0,0 @@
// This code exercises the surface area that we expect of the Error generic
// member access API. If the current toolchain is able to compile it, then
// thiserror is able to provide backtrace support.
#![no_std]
#![feature(error_generic_member_access)]
use core::error::{Error, Request};
use core::fmt::{self, Debug, Display};
struct MyError(Thing);
struct Thing;
impl Debug for MyError {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
impl Display for MyError {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
impl Error for MyError {
fn provide<'a>(&'a self, request: &mut Request<'a>) {
request.provide_ref(&self.0);
}
}
// Include in sccache cache key.
const _: Option<&str> = option_env!("RUSTC_BOOTSTRAP");

View File

@@ -1,28 +0,0 @@
[package]
name = "thiserror-impl"
version = "2.0.17"
authors = ["David Tolnay <dtolnay@gmail.com>"]
description = "Implementation detail of the `thiserror` crate"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.68"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.74"
quote = "1.0.35"
syn = "2.0.87"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = [
"--generate-link-to-definition",
"--generate-macro-expansion",
"--extern-html-root-url=core=https://doc.rust-lang.org",
"--extern-html-root-url=alloc=https://doc.rust-lang.org",
"--extern-html-root-url=std=https://doc.rust-lang.org",
"--extern-html-root-url=proc_macro=https://doc.rust-lang.org",
]

View File

@@ -1 +0,0 @@
../LICENSE-APACHE

View File

@@ -1 +0,0 @@
../LICENSE-MIT

View File

@@ -1,185 +0,0 @@
use crate::attr::{self, Attrs};
use crate::generics::ParamsInScope;
use crate::unraw::{IdentUnraw, MemberUnraw};
use proc_macro2::Span;
use std::fmt::{self, Display};
use syn::{
Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Index, Result, Type,
};
pub enum Input<'a> {
Struct(Struct<'a>),
Enum(Enum<'a>),
}
pub struct Struct<'a> {
pub attrs: Attrs<'a>,
pub ident: Ident,
pub generics: &'a Generics,
pub fields: Vec<Field<'a>>,
}
pub struct Enum<'a> {
pub attrs: Attrs<'a>,
pub ident: Ident,
pub generics: &'a Generics,
pub variants: Vec<Variant<'a>>,
}
pub struct Variant<'a> {
pub original: &'a syn::Variant,
pub attrs: Attrs<'a>,
pub ident: Ident,
pub fields: Vec<Field<'a>>,
}
pub struct Field<'a> {
pub original: &'a syn::Field,
pub attrs: Attrs<'a>,
pub member: MemberUnraw,
pub ty: &'a Type,
pub contains_generic: bool,
}
#[derive(Copy, Clone)]
pub enum ContainerKind {
Struct,
TupleStruct,
UnitStruct,
StructVariant,
TupleVariant,
UnitVariant,
}
impl<'a> Input<'a> {
pub fn from_syn(node: &'a DeriveInput) -> Result<Self> {
match &node.data {
Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct),
Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum),
Data::Union(_) => Err(Error::new_spanned(
node,
"union as errors are not supported",
)),
}
}
}
impl<'a> Struct<'a> {
fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result<Self> {
let mut attrs = attr::get(&node.attrs)?;
let scope = ParamsInScope::new(&node.generics);
let fields = Field::multiple_from_syn(&data.fields, &scope)?;
if let Some(display) = &mut attrs.display {
let container = ContainerKind::from_struct(data);
display.expand_shorthand(&fields, container)?;
}
Ok(Struct {
attrs,
ident: node.ident.clone(),
generics: &node.generics,
fields,
})
}
}
impl<'a> Enum<'a> {
fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result<Self> {
let attrs = attr::get(&node.attrs)?;
let scope = ParamsInScope::new(&node.generics);
let variants = data
.variants
.iter()
.map(|node| {
let mut variant = Variant::from_syn(node, &scope)?;
if variant.attrs.display.is_none()
&& variant.attrs.transparent.is_none()
&& variant.attrs.fmt.is_none()
{
variant.attrs.display.clone_from(&attrs.display);
variant.attrs.transparent = attrs.transparent;
variant.attrs.fmt.clone_from(&attrs.fmt);
}
if let Some(display) = &mut variant.attrs.display {
let container = ContainerKind::from_variant(node);
display.expand_shorthand(&variant.fields, container)?;
}
Ok(variant)
})
.collect::<Result<_>>()?;
Ok(Enum {
attrs,
ident: node.ident.clone(),
generics: &node.generics,
variants,
})
}
}
impl<'a> Variant<'a> {
fn from_syn(node: &'a syn::Variant, scope: &ParamsInScope<'a>) -> Result<Self> {
let attrs = attr::get(&node.attrs)?;
Ok(Variant {
original: node,
attrs,
ident: node.ident.clone(),
fields: Field::multiple_from_syn(&node.fields, scope)?,
})
}
}
impl<'a> Field<'a> {
fn multiple_from_syn(fields: &'a Fields, scope: &ParamsInScope<'a>) -> Result<Vec<Self>> {
fields
.iter()
.enumerate()
.map(|(i, field)| Field::from_syn(i, field, scope))
.collect()
}
fn from_syn(i: usize, node: &'a syn::Field, scope: &ParamsInScope<'a>) -> Result<Self> {
Ok(Field {
original: node,
attrs: attr::get(&node.attrs)?,
member: match &node.ident {
Some(name) => MemberUnraw::Named(IdentUnraw::new(name.clone())),
None => MemberUnraw::Unnamed(Index {
index: i as u32,
span: Span::call_site(),
}),
},
ty: &node.ty,
contains_generic: scope.intersects(&node.ty),
})
}
}
impl ContainerKind {
fn from_struct(node: &DataStruct) -> Self {
match node.fields {
Fields::Named(_) => ContainerKind::Struct,
Fields::Unnamed(_) => ContainerKind::TupleStruct,
Fields::Unit => ContainerKind::UnitStruct,
}
}
fn from_variant(node: &syn::Variant) -> Self {
match node.fields {
Fields::Named(_) => ContainerKind::StructVariant,
Fields::Unnamed(_) => ContainerKind::TupleVariant,
Fields::Unit => ContainerKind::UnitVariant,
}
}
}
impl Display for ContainerKind {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(match self {
ContainerKind::Struct => "struct",
ContainerKind::TupleStruct => "tuple struct",
ContainerKind::UnitStruct => "unit struct",
ContainerKind::StructVariant => "struct variant",
ContainerKind::TupleVariant => "tuple variant",
ContainerKind::UnitVariant => "unit variant",
})
}
}

View File

@@ -1,358 +0,0 @@
use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::BTreeSet as Set;
use syn::parse::discouraged::Speculative;
use syn::parse::{End, ParseStream};
use syn::{
braced, bracketed, parenthesized, token, Attribute, Error, ExprPath, Ident, Index, LitFloat,
LitInt, LitStr, Meta, Result, Token,
};
pub struct Attrs<'a> {
pub display: Option<Display<'a>>,
pub source: Option<Source<'a>>,
pub backtrace: Option<&'a Attribute>,
pub from: Option<From<'a>>,
pub transparent: Option<Transparent<'a>>,
pub fmt: Option<Fmt<'a>>,
}
#[derive(Clone)]
pub struct Display<'a> {
pub original: &'a Attribute,
pub fmt: LitStr,
pub args: TokenStream,
pub requires_fmt_machinery: bool,
pub has_bonus_display: bool,
pub infinite_recursive: bool,
pub implied_bounds: Set<(usize, Trait)>,
pub bindings: Vec<(Ident, TokenStream)>,
}
#[derive(Copy, Clone)]
pub struct Source<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[derive(Copy, Clone)]
pub struct From<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[derive(Copy, Clone)]
pub struct Transparent<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[derive(Clone)]
pub struct Fmt<'a> {
pub original: &'a Attribute,
pub path: ExprPath,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum Trait {
Debug,
Display,
Octal,
LowerHex,
UpperHex,
Pointer,
Binary,
LowerExp,
UpperExp,
}
pub fn get(input: &[Attribute]) -> Result<Attrs> {
let mut attrs = Attrs {
display: None,
source: None,
backtrace: None,
from: None,
transparent: None,
fmt: None,
};
for attr in input {
if attr.path().is_ident("error") {
parse_error_attribute(&mut attrs, attr)?;
} else if attr.path().is_ident("source") {
attr.meta.require_path_only()?;
if attrs.source.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[source] attribute"));
}
let span = (attr.pound_token.span)
.join(attr.bracket_token.span.join())
.unwrap_or(attr.path().get_ident().unwrap().span());
attrs.source = Some(Source {
original: attr,
span,
});
} else if attr.path().is_ident("backtrace") {
attr.meta.require_path_only()?;
if attrs.backtrace.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute"));
}
attrs.backtrace = Some(attr);
} else if attr.path().is_ident("from") {
match attr.meta {
Meta::Path(_) => {}
Meta::List(_) | Meta::NameValue(_) => {
// Assume this is meant for derive_more crate or something.
continue;
}
}
if attrs.from.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[from] attribute"));
}
let span = (attr.pound_token.span)
.join(attr.bracket_token.span.join())
.unwrap_or(attr.path().get_ident().unwrap().span());
attrs.from = Some(From {
original: attr,
span,
});
}
}
Ok(attrs)
}
fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> {
mod kw {
syn::custom_keyword!(transparent);
syn::custom_keyword!(fmt);
}
attr.parse_args_with(|input: ParseStream| {
let lookahead = input.lookahead1();
let fmt = if lookahead.peek(LitStr) {
input.parse::<LitStr>()?
} else if lookahead.peek(kw::transparent) {
let kw: kw::transparent = input.parse()?;
if attrs.transparent.is_some() {
return Err(Error::new_spanned(
attr,
"duplicate #[error(transparent)] attribute",
));
}
attrs.transparent = Some(Transparent {
original: attr,
span: kw.span,
});
return Ok(());
} else if lookahead.peek(kw::fmt) {
input.parse::<kw::fmt>()?;
input.parse::<Token![=]>()?;
let path: ExprPath = input.parse()?;
if attrs.fmt.is_some() {
return Err(Error::new_spanned(
attr,
"duplicate #[error(fmt = ...)] attribute",
));
}
attrs.fmt = Some(Fmt {
original: attr,
path,
});
return Ok(());
} else {
return Err(lookahead.error());
};
let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) {
input.parse::<Option<Token![,]>>()?;
TokenStream::new()
} else {
parse_token_expr(input, false)?
};
let requires_fmt_machinery = !args.is_empty();
let display = Display {
original: attr,
fmt,
args,
requires_fmt_machinery,
has_bonus_display: false,
infinite_recursive: false,
implied_bounds: Set::new(),
bindings: Vec::new(),
};
if attrs.display.is_some() {
return Err(Error::new_spanned(
attr,
"only one #[error(...)] attribute is allowed",
));
}
attrs.display = Some(display);
Ok(())
})
}
fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
let mut tokens = Vec::new();
while !input.is_empty() {
if input.peek(token::Group) {
let group: TokenTree = input.parse()?;
tokens.push(group);
begin_expr = false;
continue;
}
if begin_expr && input.peek(Token![.]) {
if input.peek2(Ident) {
input.parse::<Token![.]>()?;
begin_expr = false;
continue;
} else if input.peek2(LitInt) {
input.parse::<Token![.]>()?;
let int: Index = input.parse()?;
tokens.push({
let ident = format_ident!("_{}", int.index, span = int.span);
TokenTree::Ident(ident)
});
begin_expr = false;
continue;
} else if input.peek2(LitFloat) {
let ahead = input.fork();
ahead.parse::<Token![.]>()?;
let float: LitFloat = ahead.parse()?;
let repr = float.to_string();
let mut indices = repr.split('.').map(syn::parse_str::<Index>);
if let (Some(Ok(first)), Some(Ok(second)), None) =
(indices.next(), indices.next(), indices.next())
{
input.advance_to(&ahead);
tokens.push({
let ident = format_ident!("_{}", first, span = float.span());
TokenTree::Ident(ident)
});
tokens.push({
let mut punct = Punct::new('.', Spacing::Alone);
punct.set_span(float.span());
TokenTree::Punct(punct)
});
tokens.push({
let mut literal = Literal::u32_unsuffixed(second.index);
literal.set_span(float.span());
TokenTree::Literal(literal)
});
begin_expr = false;
continue;
}
}
}
begin_expr = input.peek(Token![break])
|| input.peek(Token![continue])
|| input.peek(Token![if])
|| input.peek(Token![in])
|| input.peek(Token![match])
|| input.peek(Token![mut])
|| input.peek(Token![return])
|| input.peek(Token![while])
|| input.peek(Token![+])
|| input.peek(Token![&])
|| input.peek(Token![!])
|| input.peek(Token![^])
|| input.peek(Token![,])
|| input.peek(Token![/])
|| input.peek(Token![=])
|| input.peek(Token![>])
|| input.peek(Token![<])
|| input.peek(Token![|])
|| input.peek(Token![%])
|| input.peek(Token![;])
|| input.peek(Token![*])
|| input.peek(Token![-]);
let token: TokenTree = if input.peek(token::Paren) {
let content;
let delimiter = parenthesized!(content in input);
let nested = parse_token_expr(&content, true)?;
let mut group = Group::new(Delimiter::Parenthesis, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else if input.peek(token::Brace) {
let content;
let delimiter = braced!(content in input);
let nested = parse_token_expr(&content, true)?;
let mut group = Group::new(Delimiter::Brace, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else if input.peek(token::Bracket) {
let content;
let delimiter = bracketed!(content in input);
let nested = parse_token_expr(&content, true)?;
let mut group = Group::new(Delimiter::Bracket, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else {
input.parse()?
};
tokens.push(token);
}
Ok(TokenStream::from_iter(tokens))
}
impl ToTokens for Display<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.infinite_recursive {
let span = self.fmt.span();
tokens.extend(quote_spanned! {span=>
#[warn(unconditional_recursion)]
fn _fmt() { _fmt() }
});
}
let fmt = &self.fmt;
let args = &self.args;
// Currently `write!(f, "text")` produces less efficient code than
// `f.write_str("text")`. We recognize the case when the format string
// has no braces and no interpolated values, and generate simpler code.
let write = if self.requires_fmt_machinery {
quote! {
::core::write!(__formatter, #fmt #args)
}
} else {
quote! {
__formatter.write_str(#fmt)
}
};
tokens.extend(if self.bindings.is_empty() {
write
} else {
let locals = self.bindings.iter().map(|(local, _value)| local);
let values = self.bindings.iter().map(|(_local, value)| value);
quote! {
match (#(#values,)*) {
(#(#locals,)*) => #write
}
}
});
}
}
impl ToTokens for Trait {
fn to_tokens(&self, tokens: &mut TokenStream) {
let trait_name = match self {
Trait::Debug => "Debug",
Trait::Display => "Display",
Trait::Octal => "Octal",
Trait::LowerHex => "LowerHex",
Trait::UpperHex => "UpperHex",
Trait::Pointer => "Pointer",
Trait::Binary => "Binary",
Trait::LowerExp => "LowerExp",
Trait::UpperExp => "UpperExp",
};
let ident = Ident::new(trait_name, Span::call_site());
tokens.extend(quote!(::core::fmt::#ident));
}
}

View File

@@ -1,584 +0,0 @@
use crate::ast::{Enum, Field, Input, Struct};
use crate::attr::Trait;
use crate::fallback;
use crate::generics::InferredBounds;
use crate::private;
use crate::unraw::MemberUnraw;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::BTreeSet as Set;
use syn::{DeriveInput, GenericArgument, PathArguments, Result, Token, Type};
pub fn derive(input: &DeriveInput) -> TokenStream {
match try_expand(input) {
Ok(expanded) => expanded,
// If there are invalid attributes in the input, expand to an Error impl
// anyway to minimize spurious secondary errors in other code that uses
// this type as an Error.
Err(error) => fallback::expand(input, error),
}
}
fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
let input = Input::from_syn(input)?;
input.validate()?;
Ok(match input {
Input::Struct(input) => impl_struct(input),
Input::Enum(input) => impl_enum(input),
})
}
fn impl_struct(input: Struct) -> TokenStream {
let ty = call_site_ident(&input.ident);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut error_inferred_bounds = InferredBounds::new();
let source_body = if let Some(transparent_attr) = &input.attrs.transparent {
let only_field = &input.fields[0];
if only_field.contains_generic {
error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::#private::Error));
}
let member = &only_field.member;
Some(quote_spanned! {transparent_attr.span=>
::thiserror::#private::Error::source(self.#member.as_dyn_error())
})
} else if let Some(source_field) = input.source_field() {
let source = &source_field.member;
if source_field.contains_generic {
let ty = unoptional_type(source_field.ty);
error_inferred_bounds.insert(ty, quote!(::thiserror::#private::Error + 'static));
}
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.span()=> .as_ref()?))
} else {
None
};
let dyn_error = quote_spanned! {source_field.source_span()=>
self.#source #asref.as_dyn_error()
};
Some(quote! {
::core::option::Option::Some(#dyn_error)
})
} else {
None
};
let source_method = source_body.map(|body| {
quote! {
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::#private::Error + 'static)> {
use ::thiserror::#private::AsDynError as _;
#body
}
}
});
let provide_method = input.backtrace_field().map(|backtrace_field| {
let request = quote!(request);
let backtrace = &backtrace_field.member;
let body = if let Some(source_field) = input.source_field() {
let source = &source_field.member;
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
if let ::core::option::Option::Some(source) = &self.#source {
source.thiserror_provide(#request);
}
}
} else {
quote_spanned! {source.span()=>
self.#source.thiserror_provide(#request);
}
};
let self_provide = if source == backtrace {
None
} else if type_is_option(backtrace_field.ty) {
Some(quote! {
if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
})
} else {
Some(quote! {
#request.provide_ref::<::thiserror::#private::Backtrace>(&self.#backtrace);
})
};
quote! {
use ::thiserror::#private::ThiserrorProvide as _;
#source_provide
#self_provide
}
} else if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<::thiserror::#private::Backtrace>(&self.#backtrace);
}
};
quote! {
fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
#body
}
}
});
let mut display_implied_bounds = Set::new();
let display_body = if input.attrs.transparent.is_some() {
let only_field = &input.fields[0].member;
display_implied_bounds.insert((0, Trait::Display));
Some(quote! {
::core::fmt::Display::fmt(&self.#only_field, __formatter)
})
} else if let Some(display) = &input.attrs.display {
display_implied_bounds.clone_from(&display.implied_bounds);
let use_as_display = use_as_display(display.has_bonus_display);
let pat = fields_pat(&input.fields);
Some(quote! {
#use_as_display
#[allow(unused_variables, deprecated)]
let Self #pat = self;
#display
})
} else {
None
};
let display_impl = display_body.map(|body| {
let mut display_inferred_bounds = InferredBounds::new();
for (field, bound) in display_implied_bounds {
let field = &input.fields[field];
if field.contains_generic {
display_inferred_bounds.insert(field.ty, bound);
}
}
let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
#[allow(clippy::used_underscore_binding)]
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
#body
}
}
}
});
let from_impl = input.from_field().map(|from_field| {
let span = from_field.attrs.from.unwrap().span;
let backtrace_field = input.distinct_backtrace_field();
let from = unoptional_type(from_field.ty);
let source_var = Ident::new("source", span);
let body = from_initializer(from_field, backtrace_field, &source_var);
let from_function = quote! {
fn from(#source_var: #from) -> Self {
#ty #body
}
};
let from_impl = quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
#from_function
}
};
Some(quote! {
#[allow(
deprecated,
unused_qualifications,
clippy::elidable_lifetime_names,
clippy::needless_lifetimes,
)]
#from_impl
})
});
if input.generics.type_params().next().is_some() {
let self_token = <Token![Self]>::default();
error_inferred_bounds.insert(self_token, Trait::Debug);
error_inferred_bounds.insert(self_token, Trait::Display);
}
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#from_impl
}
}
fn impl_enum(input: Enum) -> TokenStream {
let ty = call_site_ident(&input.ident);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut error_inferred_bounds = InferredBounds::new();
let source_method = if input.has_source() {
let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident;
if let Some(transparent_attr) = &variant.attrs.transparent {
let only_field = &variant.fields[0];
if only_field.contains_generic {
error_inferred_bounds.insert(only_field.ty, quote!(::thiserror::#private::Error));
}
let member = &only_field.member;
let source = quote_spanned! {transparent_attr.span=>
::thiserror::#private::Error::source(transparent.as_dyn_error())
};
quote! {
#ty::#ident {#member: transparent} => #source,
}
} else if let Some(source_field) = variant.source_field() {
let source = &source_field.member;
if source_field.contains_generic {
let ty = unoptional_type(source_field.ty);
error_inferred_bounds.insert(ty, quote!(::thiserror::#private::Error + 'static));
}
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.span()=> .as_ref()?))
} else {
None
};
let varsource = quote!(source);
let dyn_error = quote_spanned! {source_field.source_span()=>
#varsource #asref.as_dyn_error()
};
quote! {
#ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error),
}
} else {
quote! {
#ty::#ident {..} => ::core::option::Option::None,
}
}
});
Some(quote! {
fn source(&self) -> ::core::option::Option<&(dyn ::thiserror::#private::Error + 'static)> {
use ::thiserror::#private::AsDynError as _;
#[allow(deprecated)]
match self {
#(#arms)*
}
}
})
} else {
None
};
let provide_method = if input.has_backtrace() {
let request = quote!(request);
let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident;
match (variant.backtrace_field(), variant.source_field()) {
(Some(backtrace_field), Some(source_field))
if backtrace_field.attrs.backtrace.is_none() =>
{
let backtrace = &backtrace_field.member;
let source = &source_field.member;
let varsource = quote!(source);
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
if let ::core::option::Option::Some(source) = #varsource {
source.thiserror_provide(#request);
}
}
} else {
quote_spanned! {source.span()=>
#varsource.thiserror_provide(#request);
}
};
let self_provide = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
};
quote! {
#ty::#ident {
#backtrace: backtrace,
#source: #varsource,
..
} => {
use ::thiserror::#private::ThiserrorProvide as _;
#source_provide
#self_provide
}
}
}
(Some(backtrace_field), Some(source_field))
if backtrace_field.member == source_field.member =>
{
let backtrace = &backtrace_field.member;
let varsource = quote!(source);
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {backtrace.span()=>
if let ::core::option::Option::Some(source) = #varsource {
source.thiserror_provide(#request);
}
}
} else {
quote_spanned! {backtrace.span()=>
#varsource.thiserror_provide(#request);
}
};
quote! {
#ty::#ident {#backtrace: #varsource, ..} => {
use ::thiserror::#private::ThiserrorProvide as _;
#source_provide
}
}
}
(Some(backtrace_field), _) => {
let backtrace = &backtrace_field.member;
let body = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<::thiserror::#private::Backtrace>(backtrace);
}
};
quote! {
#ty::#ident {#backtrace: backtrace, ..} => {
#body
}
}
}
(None, _) => quote! {
#ty::#ident {..} => {}
},
}
});
Some(quote! {
fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
#[allow(deprecated)]
match self {
#(#arms)*
}
}
})
} else {
None
};
let display_impl = if input.has_display() {
let mut display_inferred_bounds = InferredBounds::new();
let has_bonus_display = input.variants.iter().any(|v| {
v.attrs
.display
.as_ref()
.map_or(false, |display| display.has_bonus_display)
});
let use_as_display = use_as_display(has_bonus_display);
let void_deref = if input.variants.is_empty() {
Some(quote!(*))
} else {
None
};
let arms = input.variants.iter().map(|variant| {
let mut display_implied_bounds = Set::new();
let display = if let Some(display) = &variant.attrs.display {
display_implied_bounds.clone_from(&display.implied_bounds);
display.to_token_stream()
} else if let Some(fmt) = &variant.attrs.fmt {
let fmt_path = &fmt.path;
let vars = variant.fields.iter().map(|field| match &field.member {
MemberUnraw::Named(ident) => ident.to_local(),
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
});
quote!(#fmt_path(#(#vars,)* __formatter))
} else {
let only_field = match &variant.fields[0].member {
MemberUnraw::Named(ident) => ident.to_local(),
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
};
display_implied_bounds.insert((0, Trait::Display));
quote!(::core::fmt::Display::fmt(#only_field, __formatter))
};
for (field, bound) in display_implied_bounds {
let field = &variant.fields[field];
if field.contains_generic {
display_inferred_bounds.insert(field.ty, bound);
}
}
let ident = &variant.ident;
let pat = fields_pat(&variant.fields);
quote! {
#ty::#ident #pat => #display
}
});
let arms = arms.collect::<Vec<_>>();
let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
Some(quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
#use_as_display
#[allow(unused_variables, deprecated, clippy::used_underscore_binding)]
match #void_deref self {
#(#arms,)*
}
}
}
})
} else {
None
};
let from_impls = input.variants.iter().filter_map(|variant| {
let from_field = variant.from_field()?;
let span = from_field.attrs.from.unwrap().span;
let backtrace_field = variant.distinct_backtrace_field();
let variant = &variant.ident;
let from = unoptional_type(from_field.ty);
let source_var = Ident::new("source", span);
let body = from_initializer(from_field, backtrace_field, &source_var);
let from_function = quote! {
fn from(#source_var: #from) -> Self {
#ty::#variant #body
}
};
let from_impl = quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
#from_function
}
};
Some(quote! {
#[allow(
deprecated,
unused_qualifications,
clippy::elidable_lifetime_names,
clippy::needless_lifetimes,
)]
#from_impl
})
});
if input.generics.type_params().next().is_some() {
let self_token = <Token![Self]>::default();
error_inferred_bounds.insert(self_token, Trait::Debug);
error_inferred_bounds.insert(self_token, Trait::Display);
}
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
quote! {
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#(#from_impls)*
}
}
// Create an ident with which we can expand `impl Trait for #ident {}` on a
// deprecated type without triggering deprecation warning on the generated impl.
pub(crate) fn call_site_ident(ident: &Ident) -> Ident {
let mut ident = ident.clone();
ident.set_span(ident.span().resolved_at(Span::call_site()));
ident
}
fn fields_pat(fields: &[Field]) -> TokenStream {
let mut members = fields.iter().map(|field| &field.member).peekable();
match members.peek() {
Some(MemberUnraw::Named(_)) => quote!({ #(#members),* }),
Some(MemberUnraw::Unnamed(_)) => {
let vars = members.map(|member| match member {
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
MemberUnraw::Named(_) => unreachable!(),
});
quote!((#(#vars),*))
}
None => quote!({}),
}
}
fn use_as_display(needs_as_display: bool) -> Option<TokenStream> {
if needs_as_display {
Some(quote! {
use ::thiserror::#private::AsDisplay as _;
})
} else {
None
}
}
fn from_initializer(
from_field: &Field,
backtrace_field: Option<&Field>,
source_var: &Ident,
) -> TokenStream {
let from_member = &from_field.member;
let some_source = if type_is_option(from_field.ty) {
quote!(::core::option::Option::Some(#source_var))
} else {
quote!(#source_var)
};
let backtrace = backtrace_field.map(|backtrace_field| {
let backtrace_member = &backtrace_field.member;
if type_is_option(backtrace_field.ty) {
quote! {
#backtrace_member: ::core::option::Option::Some(::thiserror::#private::Backtrace::capture()),
}
} else {
quote! {
#backtrace_member: ::core::convert::From::from(::thiserror::#private::Backtrace::capture()),
}
}
});
quote!({
#from_member: #some_source,
#backtrace
})
}
fn type_is_option(ty: &Type) -> bool {
type_parameter_of_option(ty).is_some()
}
fn unoptional_type(ty: &Type) -> TokenStream {
let unoptional = type_parameter_of_option(ty).unwrap_or(ty);
quote!(#unoptional)
}
fn type_parameter_of_option(ty: &Type) -> Option<&Type> {
let path = match ty {
Type::Path(ty) => &ty.path,
_ => return None,
};
let last = path.segments.last().unwrap();
if last.ident != "Option" {
return None;
}
let bracketed = match &last.arguments {
PathArguments::AngleBracketed(bracketed) => bracketed,
_ => return None,
};
if bracketed.args.len() != 1 {
return None;
}
match &bracketed.args[0] {
GenericArgument::Type(arg) => Some(arg),
_ => None,
}
}

View File

@@ -1,33 +0,0 @@
use crate::expand::call_site_ident;
use crate::private;
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
pub(crate) fn expand(input: &DeriveInput, error: syn::Error) -> TokenStream {
let ty = call_site_ident(&input.ident);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let error = error.to_compile_error();
quote! {
#error
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::thiserror::#private::Error for #ty #ty_generics #where_clause
where
// Work around trivial bounds being unstable.
// https://github.com/rust-lang/rust/issues/48214
for<'workaround> #ty #ty_generics: ::core::fmt::Debug,
{}
#[allow(unused_qualifications)]
#[automatically_derived]
impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause {
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::unreachable!()
}
}
}
}

View File

@@ -1,323 +0,0 @@
use crate::ast::{ContainerKind, Field};
use crate::attr::{Display, Trait};
use crate::private;
use crate::scan_expr::scan_expr;
use crate::unraw::{IdentUnraw, MemberUnraw};
use proc_macro2::{Delimiter, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned, ToTokens as _};
use std::collections::{BTreeSet, HashMap};
use std::iter;
use syn::ext::IdentExt;
use syn::parse::discouraged::Speculative;
use syn::parse::{Error, ParseStream, Parser, Result};
use syn::{Expr, Ident, Index, LitStr, Token};
impl Display<'_> {
pub fn expand_shorthand(&mut self, fields: &[Field], container: ContainerKind) -> Result<()> {
let raw_args = self.args.clone();
let FmtArguments {
named: user_named_args,
first_unnamed,
} = explicit_named_args.parse2(raw_args).unwrap();
let mut member_index = HashMap::new();
let mut extra_positional_arguments_allowed = true;
for (i, field) in fields.iter().enumerate() {
member_index.insert(&field.member, i);
extra_positional_arguments_allowed &= matches!(&field.member, MemberUnraw::Named(_));
}
let span = self.fmt.span();
let fmt = self.fmt.value();
let mut read = fmt.as_str();
let mut out = String::new();
let mut has_bonus_display = false;
let mut infinite_recursive = false;
let mut implied_bounds = BTreeSet::new();
let mut bindings = Vec::new();
let mut macro_named_args = BTreeSet::new();
self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}');
while let Some(brace) = read.find('{') {
self.requires_fmt_machinery = true;
out += &read[..brace + 1];
read = &read[brace + 1..];
if read.starts_with('{') {
out.push('{');
read = &read[1..];
continue;
}
let next = match read.chars().next() {
Some(next) => next,
None => return Ok(()),
};
let member = match next {
'0'..='9' => {
let int = take_int(&mut read);
if !extra_positional_arguments_allowed {
if let Some(first_unnamed) = &first_unnamed {
let msg = format!("ambiguous reference to positional arguments by number in a {container}; change this to a named argument");
return Err(Error::new_spanned(first_unnamed, msg));
}
}
match int.parse::<u32>() {
Ok(index) => MemberUnraw::Unnamed(Index { index, span }),
Err(_) => return Ok(()),
}
}
'a'..='z' | 'A'..='Z' | '_' => {
if read.starts_with("r#") {
continue;
}
let repr = take_ident(&mut read);
if repr == "_" {
// Invalid. Let rustc produce the diagnostic.
out += repr;
continue;
}
let ident = IdentUnraw::new(Ident::new(repr, span));
if user_named_args.contains(&ident) {
// Refers to a named argument written by the user, not to field.
out += repr;
continue;
}
MemberUnraw::Named(ident)
}
_ => continue,
};
let end_spec = match read.find('}') {
Some(end_spec) => end_spec,
None => return Ok(()),
};
let mut bonus_display = false;
let bound = match read[..end_spec].chars().next_back() {
Some('?') => Trait::Debug,
Some('o') => Trait::Octal,
Some('x') => Trait::LowerHex,
Some('X') => Trait::UpperHex,
Some('p') => Trait::Pointer,
Some('b') => Trait::Binary,
Some('e') => Trait::LowerExp,
Some('E') => Trait::UpperExp,
Some(_) => Trait::Display,
None => {
bonus_display = true;
has_bonus_display = true;
Trait::Display
}
};
infinite_recursive |= member == *"self" && bound == Trait::Display;
let field = match member_index.get(&member) {
Some(&field) => field,
None => {
out += &member.to_string();
continue;
}
};
implied_bounds.insert((field, bound));
let formatvar_prefix = if bonus_display {
"__display"
} else if bound == Trait::Pointer {
"__pointer"
} else {
"__field"
};
let mut formatvar = IdentUnraw::new(match &member {
MemberUnraw::Unnamed(index) => format_ident!("{}{}", formatvar_prefix, index),
MemberUnraw::Named(ident) => {
format_ident!("{}_{}", formatvar_prefix, ident.to_string())
}
});
while user_named_args.contains(&formatvar) {
formatvar = IdentUnraw::new(format_ident!("_{}", formatvar.to_string()));
}
formatvar.set_span(span);
out += &formatvar.to_string();
if !macro_named_args.insert(formatvar.clone()) {
// Already added to bindings by a previous use.
continue;
}
let mut binding_value = match &member {
MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
MemberUnraw::Named(ident) => ident.to_local(),
};
binding_value.set_span(span.resolved_at(fields[field].member.span()));
let wrapped_binding_value = if bonus_display {
quote_spanned!(span=> #binding_value.as_display())
} else if bound == Trait::Pointer {
quote!(::thiserror::#private::Var(#binding_value))
} else {
binding_value.into_token_stream()
};
bindings.push((formatvar.to_local(), wrapped_binding_value));
}
out += read;
self.fmt = LitStr::new(&out, self.fmt.span());
self.has_bonus_display = has_bonus_display;
self.infinite_recursive = infinite_recursive;
self.implied_bounds = implied_bounds;
self.bindings = bindings;
Ok(())
}
}
struct FmtArguments {
named: BTreeSet<IdentUnraw>,
first_unnamed: Option<TokenStream>,
}
#[allow(clippy::unnecessary_wraps)]
fn explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
let ahead = input.fork();
if let Ok(set) = try_explicit_named_args(&ahead) {
input.advance_to(&ahead);
return Ok(set);
}
let ahead = input.fork();
if let Ok(set) = fallback_explicit_named_args(&ahead) {
input.advance_to(&ahead);
return Ok(set);
}
input.parse::<TokenStream>().unwrap();
Ok(FmtArguments {
named: BTreeSet::new(),
first_unnamed: None,
})
}
fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
let mut syn_full = None;
let mut args = FmtArguments {
named: BTreeSet::new(),
first_unnamed: None,
};
while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
let mut begin_unnamed = None;
if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) {
let ident: IdentUnraw = input.parse()?;
input.parse::<Token![=]>()?;
args.named.insert(ident);
} else {
begin_unnamed = Some(input.fork());
}
let ahead = input.fork();
if *syn_full.get_or_insert_with(is_syn_full) && ahead.parse::<Expr>().is_ok() {
input.advance_to(&ahead);
} else {
scan_expr(input)?;
}
if let Some(begin_unnamed) = begin_unnamed {
if args.first_unnamed.is_none() {
args.first_unnamed = Some(between(&begin_unnamed, input));
}
}
}
Ok(args)
}
fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> {
let mut args = FmtArguments {
named: BTreeSet::new(),
first_unnamed: None,
};
while !input.is_empty() {
if input.peek(Token![,])
&& input.peek2(Ident::peek_any)
&& input.peek3(Token![=])
&& !input.peek3(Token![==])
{
input.parse::<Token![,]>()?;
let ident: IdentUnraw = input.parse()?;
input.parse::<Token![=]>()?;
args.named.insert(ident);
} else {
input.parse::<TokenTree>()?;
}
}
Ok(args)
}
fn is_syn_full() -> bool {
// Expr::Block contains syn::Block which contains Vec<syn::Stmt>. In the
// current version of Syn, syn::Stmt is exhaustive and could only plausibly
// represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most
// of the point of syn's non-"full" mode is to avoid compiling Item and the
// entire expansive syntax tree it comprises. So the following expression
// being parsed to Expr::Block is a reliable indication that "full" is
// enabled.
let test = quote!({
trait Trait {}
});
match syn::parse2(test) {
Ok(Expr::Verbatim(_)) | Err(_) => false,
Ok(Expr::Block(_)) => true,
Ok(_) => unreachable!(),
}
}
fn take_int<'a>(read: &mut &'a str) -> &'a str {
let mut int_len = 0;
for ch in read.chars() {
match ch {
'0'..='9' => int_len += 1,
_ => break,
}
}
let (int, rest) = read.split_at(int_len);
*read = rest;
int
}
fn take_ident<'a>(read: &mut &'a str) -> &'a str {
let mut ident_len = 0;
for ch in read.chars() {
match ch {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident_len += 1,
_ => break,
}
}
let (ident, rest) = read.split_at(ident_len);
*read = rest;
ident
}
fn between<'a>(begin: ParseStream<'a>, end: ParseStream<'a>) -> TokenStream {
let end = end.cursor();
let mut cursor = begin.cursor();
let mut tokens = TokenStream::new();
while cursor < end {
let (tt, next) = cursor.token_tree().unwrap();
if end < next {
if let Some((inside, _span, _after)) = cursor.group(Delimiter::None) {
cursor = inside;
continue;
}
if tokens.is_empty() {
tokens.extend(iter::once(tt));
}
break;
}
tokens.extend(iter::once(tt));
cursor = next;
}
tokens
}

View File

@@ -1,83 +0,0 @@
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap as Map, BTreeSet as Set};
use syn::punctuated::Punctuated;
use syn::{parse_quote, GenericArgument, Generics, Ident, PathArguments, Token, Type, WhereClause};
pub struct ParamsInScope<'a> {
names: Set<&'a Ident>,
}
impl<'a> ParamsInScope<'a> {
pub fn new(generics: &'a Generics) -> Self {
ParamsInScope {
names: generics.type_params().map(|param| &param.ident).collect(),
}
}
pub fn intersects(&self, ty: &Type) -> bool {
let mut found = false;
crawl(self, ty, &mut found);
found
}
}
fn crawl(in_scope: &ParamsInScope, ty: &Type, found: &mut bool) {
if let Type::Path(ty) = ty {
if let Some(qself) = &ty.qself {
crawl(in_scope, &qself.ty, found);
} else {
let front = ty.path.segments.first().unwrap();
if front.arguments.is_none() && in_scope.names.contains(&front.ident) {
*found = true;
}
}
for segment in &ty.path.segments {
if let PathArguments::AngleBracketed(arguments) = &segment.arguments {
for arg in &arguments.args {
if let GenericArgument::Type(ty) = arg {
crawl(in_scope, ty, found);
}
}
}
}
}
}
pub struct InferredBounds {
bounds: Map<String, (Set<String>, Punctuated<TokenStream, Token![+]>)>,
order: Vec<TokenStream>,
}
impl InferredBounds {
pub fn new() -> Self {
InferredBounds {
bounds: Map::new(),
order: Vec::new(),
}
}
pub fn insert(&mut self, ty: impl ToTokens, bound: impl ToTokens) {
let ty = ty.to_token_stream();
let bound = bound.to_token_stream();
let entry = self.bounds.entry(ty.to_string());
if let Entry::Vacant(_) = entry {
self.order.push(ty);
}
let (set, tokens) = entry.or_default();
if set.insert(bound.to_string()) {
tokens.push(bound);
}
}
pub fn augment_where_clause(&self, generics: &Generics) -> WhereClause {
let mut generics = generics.clone();
let where_clause = generics.make_where_clause();
for ty in &self.order {
let (_set, bounds) = &self.bounds[&ty.to_string()];
where_clause.predicates.push(parse_quote!(#ty: #bounds));
}
generics.where_clause.unwrap()
}
}

View File

@@ -1,52 +0,0 @@
#![allow(
clippy::blocks_in_conditions,
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::enum_glob_use,
clippy::expl_impl_clone_on_copy, // https://github.com/rust-lang/rust-clippy/issues/15842
clippy::manual_find,
clippy::manual_let_else,
clippy::manual_map,
clippy::map_unwrap_or,
clippy::module_name_repetitions,
clippy::needless_pass_by_value,
clippy::range_plus_one,
clippy::single_match_else,
clippy::struct_field_names,
clippy::too_many_lines,
clippy::wrong_self_convention
)]
#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
extern crate proc_macro;
mod ast;
mod attr;
mod expand;
mod fallback;
mod fmt;
mod generics;
mod prop;
mod scan_expr;
mod unraw;
mod valid;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{ToTokens, TokenStreamExt as _};
use syn::{DeriveInput, parse_macro_input};
#[proc_macro_derive(Error, attributes(backtrace, error, from, source))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand::derive(&input).into()
}
#[allow(non_camel_case_types)]
struct private;
impl ToTokens for private {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.append(Ident::new(concat!("__private"), Span::call_site()));
}
}

View File

@@ -1,148 +0,0 @@
use crate::ast::{Enum, Field, Struct, Variant};
use crate::unraw::MemberUnraw;
use proc_macro2::Span;
use syn::Type;
impl Struct<'_> {
pub(crate) fn from_field(&self) -> Option<&Field> {
from_field(&self.fields)
}
pub(crate) fn source_field(&self) -> Option<&Field> {
source_field(&self.fields)
}
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
backtrace_field(&self.fields)
}
pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
let backtrace_field = self.backtrace_field()?;
distinct_backtrace_field(backtrace_field, self.from_field())
}
}
impl Enum<'_> {
pub(crate) fn has_source(&self) -> bool {
self.variants
.iter()
.any(|variant| variant.source_field().is_some() || variant.attrs.transparent.is_some())
}
pub(crate) fn has_backtrace(&self) -> bool {
self.variants
.iter()
.any(|variant| variant.backtrace_field().is_some())
}
pub(crate) fn has_display(&self) -> bool {
self.attrs.display.is_some()
|| self.attrs.transparent.is_some()
|| self.attrs.fmt.is_some()
|| self
.variants
.iter()
.any(|variant| variant.attrs.display.is_some() || variant.attrs.fmt.is_some())
|| self
.variants
.iter()
.all(|variant| variant.attrs.transparent.is_some())
}
}
impl Variant<'_> {
pub(crate) fn from_field(&self) -> Option<&Field> {
from_field(&self.fields)
}
pub(crate) fn source_field(&self) -> Option<&Field> {
source_field(&self.fields)
}
pub(crate) fn backtrace_field(&self) -> Option<&Field> {
backtrace_field(&self.fields)
}
pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
let backtrace_field = self.backtrace_field()?;
distinct_backtrace_field(backtrace_field, self.from_field())
}
}
impl Field<'_> {
pub(crate) fn is_backtrace(&self) -> bool {
type_is_backtrace(self.ty)
}
pub(crate) fn source_span(&self) -> Span {
if let Some(source_attr) = &self.attrs.source {
source_attr.span
} else if let Some(from_attr) = &self.attrs.from {
from_attr.span
} else {
self.member.span()
}
}
}
fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
for field in fields {
if field.attrs.from.is_some() {
return Some(field);
}
}
None
}
fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
for field in fields {
if field.attrs.from.is_some() || field.attrs.source.is_some() {
return Some(field);
}
}
for field in fields {
match &field.member {
MemberUnraw::Named(ident) if ident == "source" => return Some(field),
_ => {}
}
}
None
}
fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
for field in fields {
if field.attrs.backtrace.is_some() {
return Some(field);
}
}
for field in fields {
if field.is_backtrace() {
return Some(field);
}
}
None
}
// The #[backtrace] field, if it is not the same as the #[from] field.
fn distinct_backtrace_field<'a, 'b>(
backtrace_field: &'a Field<'b>,
from_field: Option<&Field>,
) -> Option<&'a Field<'b>> {
if from_field.map_or(false, |from_field| {
from_field.member == backtrace_field.member
}) {
None
} else {
Some(backtrace_field)
}
}
fn type_is_backtrace(ty: &Type) -> bool {
let path = match ty {
Type::Path(ty) => &ty.path,
_ => return false,
};
let last = path.segments.last().unwrap();
last.ident == "Backtrace" && last.arguments.is_empty()
}

View File

@@ -1,264 +0,0 @@
use self::{Action::*, Input::*};
use proc_macro2::{Delimiter, Ident, Spacing, TokenTree};
use syn::parse::{ParseStream, Result};
use syn::{AngleBracketedGenericArguments, BinOp, Expr, ExprPath, Lifetime, Lit, Token, Type};
enum Input {
Keyword(&'static str),
Punct(&'static str),
ConsumeAny,
ConsumeBinOp,
ConsumeBrace,
ConsumeDelimiter,
ConsumeIdent,
ConsumeLifetime,
ConsumeLiteral,
ConsumeNestedBrace,
ExpectPath,
ExpectTurbofish,
ExpectType,
CanBeginExpr,
Otherwise,
Empty,
}
enum Action {
SetState(&'static [(Input, Action)]),
IncDepth,
DecDepth,
Finish,
}
static INIT: [(Input, Action); 28] = [
(ConsumeDelimiter, SetState(&POSTFIX)),
(Keyword("async"), SetState(&ASYNC)),
(Keyword("break"), SetState(&BREAK_LABEL)),
(Keyword("const"), SetState(&CONST)),
(Keyword("continue"), SetState(&CONTINUE)),
(Keyword("for"), SetState(&FOR)),
(Keyword("if"), IncDepth),
(Keyword("let"), SetState(&PATTERN)),
(Keyword("loop"), SetState(&BLOCK)),
(Keyword("match"), IncDepth),
(Keyword("move"), SetState(&CLOSURE)),
(Keyword("return"), SetState(&RETURN)),
(Keyword("static"), SetState(&CLOSURE)),
(Keyword("unsafe"), SetState(&BLOCK)),
(Keyword("while"), IncDepth),
(Keyword("yield"), SetState(&RETURN)),
(Keyword("_"), SetState(&POSTFIX)),
(Punct("!"), SetState(&INIT)),
(Punct("#"), SetState(&[(ConsumeDelimiter, SetState(&INIT))])),
(Punct("&"), SetState(&REFERENCE)),
(Punct("*"), SetState(&INIT)),
(Punct("-"), SetState(&INIT)),
(Punct("..="), SetState(&INIT)),
(Punct(".."), SetState(&RANGE)),
(Punct("|"), SetState(&CLOSURE_ARGS)),
(ConsumeLifetime, SetState(&[(Punct(":"), SetState(&INIT))])),
(ConsumeLiteral, SetState(&POSTFIX)),
(ExpectPath, SetState(&PATH)),
];
static POSTFIX: [(Input, Action); 10] = [
(Keyword("as"), SetState(&[(ExpectType, SetState(&POSTFIX))])),
(Punct("..="), SetState(&INIT)),
(Punct(".."), SetState(&RANGE)),
(Punct("."), SetState(&DOT)),
(Punct("?"), SetState(&POSTFIX)),
(ConsumeBinOp, SetState(&INIT)),
(Punct("="), SetState(&INIT)),
(ConsumeNestedBrace, SetState(&IF_THEN)),
(ConsumeDelimiter, SetState(&POSTFIX)),
(Empty, Finish),
];
static ASYNC: [(Input, Action); 3] = [
(Keyword("move"), SetState(&ASYNC)),
(Punct("|"), SetState(&CLOSURE_ARGS)),
(ConsumeBrace, SetState(&POSTFIX)),
];
static BLOCK: [(Input, Action); 1] = [(ConsumeBrace, SetState(&POSTFIX))];
static BREAK_LABEL: [(Input, Action); 2] = [
(ConsumeLifetime, SetState(&BREAK_VALUE)),
(Otherwise, SetState(&BREAK_VALUE)),
];
static BREAK_VALUE: [(Input, Action); 3] = [
(ConsumeNestedBrace, SetState(&IF_THEN)),
(CanBeginExpr, SetState(&INIT)),
(Otherwise, SetState(&POSTFIX)),
];
static CLOSURE: [(Input, Action); 6] = [
(Keyword("async"), SetState(&CLOSURE)),
(Keyword("move"), SetState(&CLOSURE)),
(Punct(","), SetState(&CLOSURE)),
(Punct(">"), SetState(&CLOSURE)),
(Punct("|"), SetState(&CLOSURE_ARGS)),
(ConsumeLifetime, SetState(&CLOSURE)),
];
static CLOSURE_ARGS: [(Input, Action); 2] = [
(Punct("|"), SetState(&CLOSURE_RET)),
(ConsumeAny, SetState(&CLOSURE_ARGS)),
];
static CLOSURE_RET: [(Input, Action); 2] = [
(Punct("->"), SetState(&[(ExpectType, SetState(&BLOCK))])),
(Otherwise, SetState(&INIT)),
];
static CONST: [(Input, Action); 2] = [
(Punct("|"), SetState(&CLOSURE_ARGS)),
(ConsumeBrace, SetState(&POSTFIX)),
];
static CONTINUE: [(Input, Action); 2] = [
(ConsumeLifetime, SetState(&POSTFIX)),
(Otherwise, SetState(&POSTFIX)),
];
static DOT: [(Input, Action); 3] = [
(Keyword("await"), SetState(&POSTFIX)),
(ConsumeIdent, SetState(&METHOD)),
(ConsumeLiteral, SetState(&POSTFIX)),
];
static FOR: [(Input, Action); 2] = [
(Punct("<"), SetState(&CLOSURE)),
(Otherwise, SetState(&PATTERN)),
];
static IF_ELSE: [(Input, Action); 2] = [(Keyword("if"), SetState(&INIT)), (ConsumeBrace, DecDepth)];
static IF_THEN: [(Input, Action); 2] =
[(Keyword("else"), SetState(&IF_ELSE)), (Otherwise, DecDepth)];
static METHOD: [(Input, Action); 1] = [(ExpectTurbofish, SetState(&POSTFIX))];
static PATH: [(Input, Action); 4] = [
(Punct("!="), SetState(&INIT)),
(Punct("!"), SetState(&INIT)),
(ConsumeNestedBrace, SetState(&IF_THEN)),
(Otherwise, SetState(&POSTFIX)),
];
static PATTERN: [(Input, Action); 15] = [
(ConsumeDelimiter, SetState(&PATTERN)),
(Keyword("box"), SetState(&PATTERN)),
(Keyword("in"), IncDepth),
(Keyword("mut"), SetState(&PATTERN)),
(Keyword("ref"), SetState(&PATTERN)),
(Keyword("_"), SetState(&PATTERN)),
(Punct("!"), SetState(&PATTERN)),
(Punct("&"), SetState(&PATTERN)),
(Punct("..="), SetState(&PATTERN)),
(Punct(".."), SetState(&PATTERN)),
(Punct("="), SetState(&INIT)),
(Punct("@"), SetState(&PATTERN)),
(Punct("|"), SetState(&PATTERN)),
(ConsumeLiteral, SetState(&PATTERN)),
(ExpectPath, SetState(&PATTERN)),
];
static RANGE: [(Input, Action); 6] = [
(Punct("..="), SetState(&INIT)),
(Punct(".."), SetState(&RANGE)),
(Punct("."), SetState(&DOT)),
(ConsumeNestedBrace, SetState(&IF_THEN)),
(Empty, Finish),
(Otherwise, SetState(&INIT)),
];
static RAW: [(Input, Action); 3] = [
(Keyword("const"), SetState(&INIT)),
(Keyword("mut"), SetState(&INIT)),
(Otherwise, SetState(&POSTFIX)),
];
static REFERENCE: [(Input, Action); 3] = [
(Keyword("mut"), SetState(&INIT)),
(Keyword("raw"), SetState(&RAW)),
(Otherwise, SetState(&INIT)),
];
static RETURN: [(Input, Action); 2] = [
(CanBeginExpr, SetState(&INIT)),
(Otherwise, SetState(&POSTFIX)),
];
pub(crate) fn scan_expr(input: ParseStream) -> Result<()> {
let mut state = INIT.as_slice();
let mut depth = 0usize;
'table: loop {
for rule in state {
if match rule.0 {
Input::Keyword(expected) => input.step(|cursor| match cursor.ident() {
Some((ident, rest)) if ident == expected => Ok((true, rest)),
_ => Ok((false, *cursor)),
})?,
Input::Punct(expected) => input.step(|cursor| {
let begin = *cursor;
let mut cursor = begin;
for (i, ch) in expected.chars().enumerate() {
match cursor.punct() {
Some((punct, _)) if punct.as_char() != ch => break,
Some((_, rest)) if i == expected.len() - 1 => {
return Ok((true, rest));
}
Some((punct, rest)) if punct.spacing() == Spacing::Joint => {
cursor = rest;
}
_ => break,
}
}
Ok((false, begin))
})?,
Input::ConsumeAny => input.parse::<Option<TokenTree>>()?.is_some(),
Input::ConsumeBinOp => input.parse::<BinOp>().is_ok(),
Input::ConsumeBrace | Input::ConsumeNestedBrace => {
(matches!(rule.0, Input::ConsumeBrace) || depth > 0)
&& input.step(|cursor| match cursor.group(Delimiter::Brace) {
Some((_inside, _span, rest)) => Ok((true, rest)),
None => Ok((false, *cursor)),
})?
}
Input::ConsumeDelimiter => input.step(|cursor| match cursor.any_group() {
Some((_inside, _delimiter, _span, rest)) => Ok((true, rest)),
None => Ok((false, *cursor)),
})?,
Input::ConsumeIdent => input.parse::<Option<Ident>>()?.is_some(),
Input::ConsumeLifetime => input.parse::<Option<Lifetime>>()?.is_some(),
Input::ConsumeLiteral => input.parse::<Option<Lit>>()?.is_some(),
Input::ExpectPath => {
input.parse::<ExprPath>()?;
true
}
Input::ExpectTurbofish => {
if input.peek(Token![::]) {
input.parse::<AngleBracketedGenericArguments>()?;
}
true
}
Input::ExpectType => {
Type::without_plus(input)?;
true
}
Input::CanBeginExpr => Expr::peek(input),
Input::Otherwise => true,
Input::Empty => input.is_empty() || input.peek(Token![,]),
} {
state = match rule.1 {
Action::SetState(next) => next,
Action::IncDepth => (depth += 1, &INIT).1,
Action::DecDepth => (depth -= 1, &POSTFIX).1,
Action::Finish => return if depth == 0 { Ok(()) } else { break },
};
continue 'table;
}
}
return Err(input.error("unsupported expression"));
}
}

View File

@@ -1,142 +0,0 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use std::cmp::Ordering;
use std::fmt::{self, Display};
use std::hash::{Hash, Hasher};
use syn::ext::IdentExt as _;
use syn::parse::{Parse, ParseStream, Result};
use syn::Index;
#[derive(Clone)]
#[repr(transparent)]
pub struct IdentUnraw(Ident);
impl IdentUnraw {
pub fn new(ident: Ident) -> Self {
IdentUnraw(ident)
}
pub fn to_local(&self) -> Ident {
let unraw = self.0.unraw();
let repr = unraw.to_string();
if syn::parse_str::<Ident>(&repr).is_err() {
if let "_" | "super" | "self" | "Self" | "crate" = repr.as_str() {
// Some identifiers are never allowed to appear as raw, like r#self and r#_.
} else {
return Ident::new_raw(&repr, Span::call_site());
}
}
unraw
}
pub fn set_span(&mut self, span: Span) {
self.0.set_span(span);
}
}
impl Display for IdentUnraw {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0.unraw(), formatter)
}
}
impl Eq for IdentUnraw {}
impl PartialEq for IdentUnraw {
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.0.unraw(), &other.0.unraw())
}
}
impl PartialEq<str> for IdentUnraw {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl Ord for IdentUnraw {
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(&self.0.unraw(), &other.0.unraw())
}
}
impl PartialOrd for IdentUnraw {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Self::cmp(self, other))
}
}
impl Parse for IdentUnraw {
fn parse(input: ParseStream) -> Result<Self> {
input.call(Ident::parse_any).map(IdentUnraw::new)
}
}
impl ToTokens for IdentUnraw {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.unraw().to_tokens(tokens);
}
}
#[derive(Clone)]
pub enum MemberUnraw {
Named(IdentUnraw),
Unnamed(Index),
}
impl MemberUnraw {
pub fn span(&self) -> Span {
match self {
MemberUnraw::Named(ident) => ident.0.span(),
MemberUnraw::Unnamed(index) => index.span,
}
}
}
impl Display for MemberUnraw {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
MemberUnraw::Named(this) => Display::fmt(this, formatter),
MemberUnraw::Unnamed(this) => Display::fmt(&this.index, formatter),
}
}
}
impl Eq for MemberUnraw {}
impl PartialEq for MemberUnraw {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(MemberUnraw::Named(this), MemberUnraw::Named(other)) => this == other,
(MemberUnraw::Unnamed(this), MemberUnraw::Unnamed(other)) => this == other,
_ => false,
}
}
}
impl PartialEq<str> for MemberUnraw {
fn eq(&self, other: &str) -> bool {
match self {
MemberUnraw::Named(this) => this == other,
MemberUnraw::Unnamed(_) => false,
}
}
}
impl Hash for MemberUnraw {
fn hash<H: Hasher>(&self, hasher: &mut H) {
match self {
MemberUnraw::Named(ident) => ident.0.unraw().hash(hasher),
MemberUnraw::Unnamed(index) => index.hash(hasher),
}
}
}
impl ToTokens for MemberUnraw {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
MemberUnraw::Named(ident) => ident.to_local().to_tokens(tokens),
MemberUnraw::Unnamed(index) => index.to_tokens(tokens),
}
}
}

View File

@@ -1,248 +0,0 @@
use crate::ast::{Enum, Field, Input, Struct, Variant};
use crate::attr::Attrs;
use syn::{Error, GenericArgument, PathArguments, Result, Type};
impl Input<'_> {
pub(crate) fn validate(&self) -> Result<()> {
match self {
Input::Struct(input) => input.validate(),
Input::Enum(input) => input.validate(),
}
}
}
impl Struct<'_> {
fn validate(&self) -> Result<()> {
check_non_field_attrs(&self.attrs)?;
if let Some(transparent) = self.attrs.transparent {
if self.fields.len() != 1 {
return Err(Error::new_spanned(
transparent.original,
"#[error(transparent)] requires exactly one field",
));
}
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
return Err(Error::new_spanned(
source.original,
"transparent error struct can't contain #[source]",
));
}
}
if let Some(fmt) = &self.attrs.fmt {
return Err(Error::new_spanned(
fmt.original,
"#[error(fmt = ...)] is only supported in enums; for a struct, handwrite your own Display impl",
));
}
check_field_attrs(&self.fields)?;
for field in &self.fields {
field.validate()?;
}
Ok(())
}
}
impl Enum<'_> {
fn validate(&self) -> Result<()> {
check_non_field_attrs(&self.attrs)?;
let has_display = self.has_display();
for variant in &self.variants {
variant.validate()?;
if has_display
&& variant.attrs.display.is_none()
&& variant.attrs.transparent.is_none()
&& variant.attrs.fmt.is_none()
{
return Err(Error::new_spanned(
variant.original,
"missing #[error(\"...\")] display attribute",
));
}
}
Ok(())
}
}
impl Variant<'_> {
fn validate(&self) -> Result<()> {
check_non_field_attrs(&self.attrs)?;
if self.attrs.transparent.is_some() {
if self.fields.len() != 1 {
return Err(Error::new_spanned(
self.original,
"#[error(transparent)] requires exactly one field",
));
}
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
return Err(Error::new_spanned(
source.original,
"transparent variant can't contain #[source]",
));
}
}
check_field_attrs(&self.fields)?;
for field in &self.fields {
field.validate()?;
}
Ok(())
}
}
impl Field<'_> {
fn validate(&self) -> Result<()> {
if let Some(unexpected_display_attr) = if let Some(display) = &self.attrs.display {
Some(display.original)
} else if let Some(fmt) = &self.attrs.fmt {
Some(fmt.original)
} else {
None
} {
return Err(Error::new_spanned(
unexpected_display_attr,
"not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant",
));
}
Ok(())
}
}
fn check_non_field_attrs(attrs: &Attrs) -> Result<()> {
if let Some(from) = &attrs.from {
return Err(Error::new_spanned(
from.original,
"not expected here; the #[from] attribute belongs on a specific field",
));
}
if let Some(source) = &attrs.source {
return Err(Error::new_spanned(
source.original,
"not expected here; the #[source] attribute belongs on a specific field",
));
}
if let Some(backtrace) = &attrs.backtrace {
return Err(Error::new_spanned(
backtrace,
"not expected here; the #[backtrace] attribute belongs on a specific field",
));
}
if attrs.transparent.is_some() {
if let Some(display) = &attrs.display {
return Err(Error::new_spanned(
display.original,
"cannot have both #[error(transparent)] and a display attribute",
));
}
if let Some(fmt) = &attrs.fmt {
return Err(Error::new_spanned(
fmt.original,
"cannot have both #[error(transparent)] and #[error(fmt = ...)]",
));
}
} else if let (Some(display), Some(_)) = (&attrs.display, &attrs.fmt) {
return Err(Error::new_spanned(
display.original,
"cannot have both #[error(fmt = ...)] and a format arguments attribute",
));
}
Ok(())
}
fn check_field_attrs(fields: &[Field]) -> Result<()> {
let mut from_field = None;
let mut source_field = None;
let mut backtrace_field = None;
let mut has_backtrace = false;
for field in fields {
if let Some(from) = field.attrs.from {
if from_field.is_some() {
return Err(Error::new_spanned(
from.original,
"duplicate #[from] attribute",
));
}
from_field = Some(field);
}
if let Some(source) = field.attrs.source {
if source_field.is_some() {
return Err(Error::new_spanned(
source.original,
"duplicate #[source] attribute",
));
}
source_field = Some(field);
}
if let Some(backtrace) = field.attrs.backtrace {
if backtrace_field.is_some() {
return Err(Error::new_spanned(
backtrace,
"duplicate #[backtrace] attribute",
));
}
backtrace_field = Some(field);
has_backtrace = true;
}
if let Some(transparent) = field.attrs.transparent {
return Err(Error::new_spanned(
transparent.original,
"#[error(transparent)] needs to go outside the enum or struct, not on an individual field",
));
}
has_backtrace |= field.is_backtrace();
}
if let (Some(from_field), Some(source_field)) = (from_field, source_field) {
if from_field.member != source_field.member {
return Err(Error::new_spanned(
from_field.attrs.from.unwrap().original,
"#[from] is only supported on the source field, not any other field",
));
}
}
if let Some(from_field) = from_field {
let max_expected_fields = match backtrace_field {
Some(backtrace_field) => 1 + (from_field.member != backtrace_field.member) as usize,
None => 1 + has_backtrace as usize,
};
if fields.len() > max_expected_fields {
return Err(Error::new_spanned(
from_field.attrs.from.unwrap().original,
"deriving From requires no fields other than source and backtrace",
));
}
}
if let Some(source_field) = source_field.or(from_field) {
if contains_non_static_lifetime(source_field.ty) {
return Err(Error::new_spanned(
&source_field.original.ty,
"non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static",
));
}
}
Ok(())
}
fn contains_non_static_lifetime(ty: &Type) -> bool {
match ty {
Type::Path(ty) => {
let bracketed = match &ty.path.segments.last().unwrap().arguments {
PathArguments::AngleBracketed(bracketed) => bracketed,
_ => return false,
};
for arg in &bracketed.args {
match arg {
GenericArgument::Type(ty) if contains_non_static_lifetime(ty) => return true,
GenericArgument::Lifetime(lifetime) if lifetime.ident != "static" => {
return true
}
_ => {}
}
}
false
}
Type::Reference(ty) => ty
.lifetime
.as_ref()
.map_or(false, |lifetime| lifetime.ident != "static"),
_ => false, // maybe implement later if there are common other cases
}
}

View File

@@ -1,31 +0,0 @@
project(
'thiserror',
'rust',
version: '2.0.17',
meson_version: '>=1.9.1',
default_options: {
'rust_std': '2024',
'rust_nightly': 'enabled',
'rust_edition': 2024,
},
)
rust = import('rust')
syn = dependency('syn-2-rs')
proc_macro2 = dependency('proc-macro2-1-rs')
quote = dependency('quote-1-rs')
thiserror_proc_macro = rust.proc_macro(
'thiserror_impl',
'impl/src/lib.rs',
dependencies: [syn, proc_macro2, quote],
)
thiserror = static_library(
'thiserror',
'src/lib.rs',
rust_args: ['--cfg', 'feature="std"'],
link_with: [thiserror_proc_macro],
)

View File

@@ -1,2 +0,0 @@
[toolchain]
components = ["rust-src"]

View File

@@ -1,50 +0,0 @@
use core::error::Error;
use core::panic::UnwindSafe;
#[doc(hidden)]
pub trait AsDynError<'a>: Sealed {
fn as_dyn_error(&self) -> &(dyn Error + 'a);
}
impl<'a, T: Error + 'a> AsDynError<'a> for T {
#[inline]
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
self
}
}
impl<'a> AsDynError<'a> for dyn Error + 'a {
#[inline]
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
self
}
}
impl<'a> AsDynError<'a> for dyn Error + Send + 'a {
#[inline]
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
self
}
}
impl<'a> AsDynError<'a> for dyn Error + Send + Sync + 'a {
#[inline]
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
self
}
}
impl<'a> AsDynError<'a> for dyn Error + Send + Sync + UnwindSafe + 'a {
#[inline]
fn as_dyn_error(&self) -> &(dyn Error + 'a) {
self
}
}
#[doc(hidden)]
pub trait Sealed {}
impl<T: Error> Sealed for T {}
impl Sealed for dyn Error + '_ {}
impl Sealed for dyn Error + Send + '_ {}
impl Sealed for dyn Error + Send + Sync + '_ {}
impl Sealed for dyn Error + Send + Sync + UnwindSafe + '_ {}

View File

@@ -1,82 +0,0 @@
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::{self, Path, PathBuf};
#[doc(hidden)]
pub trait AsDisplay<'a>: Sealed {
// TODO: convert to generic associated type.
// https://github.com/dtolnay/thiserror/pull/253
type Target: Display;
fn as_display(&'a self) -> Self::Target;
}
impl<'a, T> AsDisplay<'a> for &T
where
T: Display + ?Sized + 'a,
{
type Target = &'a T;
fn as_display(&'a self) -> Self::Target {
*self
}
}
#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for Path {
type Target = path::Display<'a>;
#[inline]
fn as_display(&'a self) -> Self::Target {
self.display()
}
}
#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for PathBuf {
type Target = path::Display<'a>;
#[inline]
fn as_display(&'a self) -> Self::Target {
self.display()
}
}
#[doc(hidden)]
pub trait Sealed {}
impl<T: Display + ?Sized> Sealed for &T {}
#[cfg(feature = "std")]
impl Sealed for Path {}
#[cfg(feature = "std")]
impl Sealed for PathBuf {}
// Add a synthetic second impl of AsDisplay to prevent the "single applicable
// impl" rule from making too weird inference decision based on the single impl
// for &T, which could lead to code that compiles with thiserror's std feature
// off but breaks under feature unification when std is turned on by an
// unrelated crate.
#[cfg(not(feature = "std"))]
mod placeholder {
use super::{AsDisplay, Sealed};
use core::fmt::{self, Display};
#[allow(dead_code)]
pub struct Placeholder;
impl<'a> AsDisplay<'a> for Placeholder {
type Target = Self;
#[inline]
fn as_display(&'a self) -> Self::Target {
Placeholder
}
}
impl Display for Placeholder {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unreachable!()
}
}
impl Sealed for Placeholder {}
}

View File

@@ -1,295 +0,0 @@
//! [![github]](https://github.com/dtolnay/thiserror)&ensp;[![crates-io]](https://crates.io/crates/thiserror)&ensp;[![docs-rs]](https://docs.rs/thiserror)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
//! This library provides a convenient derive macro for the standard library's
//! [`std::error::Error`] trait.
//!
//! <br>
//!
//! # Example
//!
//! ```rust
//! # use std::io;
//! use thiserror::Error;
//!
//! #[derive(Error, Debug)]
//! pub enum DataStoreError {
//! #[error("data store disconnected")]
//! Disconnect(#[from] io::Error),
//! #[error("the data for key `{0}` is not available")]
//! Redaction(String),
//! #[error("invalid header (expected {expected:?}, found {found:?})")]
//! InvalidHeader {
//! expected: String,
//! found: String,
//! },
//! #[error("unknown data store error")]
//! Unknown,
//! }
//! ```
//!
//! <br>
//!
//! # Details
//!
//! - Thiserror deliberately does not appear in your public API. You get the
//! same thing as if you had written an implementation of
//! [`std::error::Error`] by hand, and switching from handwritten impls to
//! thiserror or vice versa is not a breaking change.
//!
//! - Errors may be enums, structs with named fields, tuple structs, or unit
//! structs.
//!
//! - A [`Display`] impl is generated for your error if you provide
//! `#[error("...")]` messages on the struct or each variant of your enum, as
//! shown above in the example.
//!
//! The messages support a shorthand for interpolating fields from the error.
//!
//! - `#[error("{var}")]`&ensp;⟶&ensp;`write!("{}", self.var)`
//! - `#[error("{0}")]`&ensp;⟶&ensp;`write!("{}", self.0)`
//! - `#[error("{var:?}")]`&ensp;⟶&ensp;`write!("{:?}", self.var)`
//! - `#[error("{0:?}")]`&ensp;⟶&ensp;`write!("{:?}", self.0)`
//!
//! These shorthands can be used together with any additional format args,
//! which may be arbitrary expressions. For example:
//!
//! ```rust
//! # use core::i32;
//! # use thiserror::Error;
//! #
//! #[derive(Error, Debug)]
//! pub enum Error {
//! #[error("invalid rdo_lookahead_frames {0} (expected < {max})", max = i32::MAX)]
//! InvalidLookahead(u32),
//! }
//! ```
//!
//! If one of the additional expression arguments needs to refer to a field of
//! the struct or enum, then refer to named fields as `.var` and tuple fields
//! as `.0`.
//!
//! ```rust
//! # use thiserror::Error;
//! #
//! # fn first_char(s: &String) -> char {
//! # s.chars().next().unwrap()
//! # }
//! #
//! # #[derive(Debug)]
//! # struct Limits {
//! # lo: usize,
//! # hi: usize,
//! # }
//! #
//! #[derive(Error, Debug)]
//! pub enum Error {
//! #[error("first letter must be lowercase but was {:?}", first_char(.0))]
//! WrongCase(String),
//! #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
//! OutOfBounds { idx: usize, limits: Limits },
//! }
//! ```
//!
//! - A [`From`] impl is generated for each variant that contains a `#[from]`
//! attribute.
//!
//! The variant using `#[from]` must not contain any other fields beyond the
//! source error (and possibly a backtrace &mdash; see below). Usually
//! `#[from]` fields are unnamed, but `#[from]` is allowed on a named field
//! too.
//!
//! ```rust
//! # use core::fmt::{self, Display};
//! # use std::io;
//! # use thiserror::Error;
//! #
//! # mod globset {
//! # #[derive(thiserror::Error, Debug)]
//! # #[error("...")]
//! # pub struct Error;
//! # }
//! #
//! #[derive(Error, Debug)]
//! pub enum MyError {
//! Io(#[from] io::Error),
//! Glob(#[from] globset::Error),
//! }
//! #
//! # impl Display for MyError {
//! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
//! # unimplemented!()
//! # }
//! # }
//! ```
//!
//! - The Error trait's [`source()`] method is implemented to return whichever
//! field has a `#[source]` attribute or is named `source`, if any. This is
//! for identifying the underlying lower level error that caused your error.
//!
//! The `#[from]` attribute always implies that the same field is `#[source]`,
//! so you don't ever need to specify both attributes.
//!
//! Any error type that implements `std::error::Error` or dereferences to `dyn
//! std::error::Error` will work as a source.
//!
//! ```rust
//! # use core::fmt::{self, Display};
//! # use thiserror::Error;
//! #
//! #[derive(Error, Debug)]
//! pub struct MyError {
//! msg: String,
//! #[source] // optional if field name is `source`
//! source: anyhow::Error,
//! }
//! #
//! # impl Display for MyError {
//! # fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
//! # unimplemented!()
//! # }
//! # }
//! ```
//!
//! - The Error trait's [`provide()`] method is implemented to provide whichever
//! field has a type named `Backtrace`, if any, as a
//! [`std::backtrace::Backtrace`]. Using `Backtrace` in errors requires a
//! nightly compiler with Rust version 1.73 or newer.
//!
//! ```rust
//! # const IGNORE: &str = stringify! {
//! use std::backtrace::Backtrace;
//!
//! #[derive(Error, Debug)]
//! pub struct MyError {
//! msg: String,
//! backtrace: Backtrace, // automatically detected
//! }
//! # };
//! ```
//!
//! - If a field is both a source (named `source`, or has `#[source]` or
//! `#[from]` attribute) *and* is marked `#[backtrace]`, then the Error
//! trait's [`provide()`] method is forwarded to the source's `provide` so
//! that both layers of the error share the same backtrace. The `#[backtrace]`
//! attribute requires a nightly compiler with Rust version 1.73 or newer.
//!
//! ```rust
//! # const IGNORE: &str = stringify! {
//! #[derive(Error, Debug)]
//! pub enum MyError {
//! Io {
//! #[backtrace]
//! source: io::Error,
//! },
//! }
//! # };
//! ```
//!
//! - For variants that use `#[from]` and also contain a `Backtrace` field, a
//! backtrace is captured from within the `From` impl.
//!
//! ```rust
//! # const IGNORE: &str = stringify! {
//! #[derive(Error, Debug)]
//! pub enum MyError {
//! Io {
//! #[from]
//! source: io::Error,
//! backtrace: Backtrace,
//! },
//! }
//! # };
//! ```
//!
//! - Errors may use `error(transparent)` to forward the source and [`Display`]
//! methods straight through to an underlying error without adding an
//! additional message. This would be appropriate for enums that need an
//! "anything else" variant.
//!
//! ```
//! # use thiserror::Error;
//! #
//! #[derive(Error, Debug)]
//! pub enum MyError {
//! # /*
//! ...
//! # */
//!
//! #[error(transparent)]
//! Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
//! }
//! ```
//!
//! Another use case is hiding implementation details of an error
//! representation behind an opaque error type, so that the representation is
//! able to evolve without breaking the crate's public API.
//!
//! ```
//! # use thiserror::Error;
//! #
//! // PublicError is public, but opaque and easy to keep compatible.
//! #[derive(Error, Debug)]
//! #[error(transparent)]
//! pub struct PublicError(#[from] ErrorRepr);
//!
//! impl PublicError {
//! // Accessors for anything we do want to expose publicly.
//! }
//!
//! // Private and free to change across minor version of the crate.
//! #[derive(Error, Debug)]
//! enum ErrorRepr {
//! # /*
//! ...
//! # */
//! }
//! ```
//!
//! - See also the [`anyhow`] library for a convenient single error type to use
//! in application code.
//!
//! [`anyhow`]: https://github.com/dtolnay/anyhow
//! [`source()`]: std::error::Error::source
//! [`provide()`]: std::error::Error::provide
//! [`Display`]: std::fmt::Display
#![no_std]
#![doc(html_root_url = "https://docs.rs/thiserror/2.0.17")]
#![allow(
clippy::elidable_lifetime_names,
clippy::module_name_repetitions,
clippy::needless_lifetimes,
clippy::return_self_not_must_use,
clippy::wildcard_imports
)]
#![cfg_attr(error_generic_member_access, feature(error_generic_member_access))]
#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
compile_error!("Build script probe failed to compile.");
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
extern crate std as core;
mod aserror;
mod display;
#[cfg(error_generic_member_access)]
mod provide;
mod var;
pub use thiserror_impl::*;
mod private;
#[doc(hidden)]
pub mod __private {
#[doc(hidden)]
pub use crate::private::*;
}

View File

@@ -1,14 +0,0 @@
#[doc(hidden)]
pub use crate::aserror::AsDynError;
#[doc(hidden)]
pub use crate::display::AsDisplay;
#[cfg(error_generic_member_access)]
#[doc(hidden)]
pub use crate::provide::ThiserrorProvide;
#[doc(hidden)]
pub use crate::var::Var;
#[doc(hidden)]
pub use core::error::Error;
#[cfg(all(feature = "std", not(thiserror_no_backtrace_type)))]
#[doc(hidden)]
pub use std::backtrace::Backtrace;

View File

@@ -1,20 +0,0 @@
use core::error::{Error, Request};
#[doc(hidden)]
pub trait ThiserrorProvide: Sealed {
fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>);
}
impl<T> ThiserrorProvide for T
where
T: Error + ?Sized,
{
#[inline]
fn thiserror_provide<'a>(&'a self, request: &mut Request<'a>) {
self.provide(request);
}
}
#[doc(hidden)]
pub trait Sealed {}
impl<T: Error + ?Sized> Sealed for T {}

View File

@@ -1,9 +0,0 @@
use core::fmt::{self, Pointer};
pub struct Var<'a, T: ?Sized>(pub &'a T);
impl<'a, T: Pointer + ?Sized> Pointer for Var<'a, T> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Pointer::fmt(self.0, formatter)
}
}

View File

@@ -1,7 +0,0 @@
#[rustversion::attr(not(nightly), ignore = "requires nightly")]
#[cfg_attr(miri, ignore = "incompatible with miri")]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}

View File

@@ -1,12 +0,0 @@
[package]
name = "thiserror_no_std_test"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2021"
publish = false
[lib]
path = "test.rs"
[dependencies]
thiserror = { path = "../..", default-features = false }

View File

@@ -1,58 +0,0 @@
#![no_std]
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Error::E")]
E(#[from] SourceError),
}
#[derive(Error, Debug)]
#[error("SourceError {field}")]
pub struct SourceError {
pub field: i32,
}
#[cfg(test)]
mod tests {
use crate::{Error, SourceError};
use core::error::Error as _;
use core::fmt::{self, Write};
use core::mem;
struct Buf<'a>(&'a mut [u8]);
impl Write for Buf<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if s.len() <= self.0.len() {
let (out, rest) = mem::take(&mut self.0).split_at_mut(s.len());
out.copy_from_slice(s.as_bytes());
self.0 = rest;
Ok(())
} else {
Err(fmt::Error)
}
}
}
#[test]
fn test() {
let source = SourceError { field: -1 };
let error = Error::from(source);
let source = error
.source()
.unwrap()
.downcast_ref::<SourceError>()
.unwrap();
let mut msg = [b'~'; 17];
write!(Buf(&mut msg), "{error}").unwrap();
assert_eq!(msg, *b"Error::E~~~~~~~~~");
let mut msg = [b'~'; 17];
write!(Buf(&mut msg), "{source}").unwrap();
assert_eq!(msg, *b"SourceError -1~~~");
}
}

View File

@@ -1,289 +0,0 @@
#![cfg(feature = "std")]
#![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))]
use thiserror::Error;
#[derive(Error, Debug)]
#[error("...")]
pub struct Inner;
#[cfg(thiserror_nightly_testing)]
#[derive(Error, Debug)]
#[error("...")]
pub struct InnerBacktrace {
backtrace: std::backtrace::Backtrace,
}
#[cfg(thiserror_nightly_testing)]
pub mod structs {
use super::{Inner, InnerBacktrace};
use std::backtrace::Backtrace;
use std::error::{self, Error};
use std::sync::Arc;
use thiserror::Error;
mod not_backtrace {
#[derive(Debug)]
pub struct Backtrace;
}
#[derive(Error, Debug)]
#[error("...")]
pub struct PlainBacktrace {
backtrace: Backtrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct ExplicitBacktrace {
#[backtrace]
backtrace: Backtrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct NotBacktrace {
backtrace: crate::structs::not_backtrace::r#Backtrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct OptBacktrace {
#[backtrace]
backtrace: Option<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct ArcBacktrace {
#[backtrace]
backtrace: Arc<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct BacktraceFrom {
#[from]
source: Inner,
#[backtrace]
backtrace: Backtrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct CombinedBacktraceFrom {
#[from]
#[backtrace]
source: InnerBacktrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct OptBacktraceFrom {
#[from]
source: Inner,
#[backtrace]
backtrace: Option<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct ArcBacktraceFrom {
#[from]
source: Inner,
#[backtrace]
backtrace: Arc<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct AnyhowBacktrace {
#[backtrace]
source: anyhow::Error,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct BoxDynErrorBacktrace {
#[backtrace]
source: Box<dyn Error>,
}
#[test]
fn test_backtrace() {
let error = PlainBacktrace {
backtrace: Backtrace::capture(),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ExplicitBacktrace {
backtrace: Backtrace::capture(),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = OptBacktrace {
backtrace: Some(Backtrace::capture()),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ArcBacktrace {
backtrace: Arc::new(Backtrace::capture()),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = BacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = CombinedBacktraceFrom::from(InnerBacktrace {
backtrace: Backtrace::capture(),
});
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = OptBacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ArcBacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = AnyhowBacktrace {
source: anyhow::Error::msg("..."),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = BoxDynErrorBacktrace {
source: Box::new(PlainBacktrace {
backtrace: Backtrace::capture(),
}),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
}
}
#[cfg(thiserror_nightly_testing)]
pub mod enums {
use super::{Inner, InnerBacktrace};
use std::backtrace::Backtrace;
use std::error;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PlainBacktrace {
#[error("...")]
Test { backtrace: Backtrace },
}
#[derive(Error, Debug)]
pub enum ExplicitBacktrace {
#[error("...")]
Test {
#[backtrace]
backtrace: Backtrace,
},
}
#[derive(Error, Debug)]
pub enum OptBacktrace {
#[error("...")]
Test {
#[backtrace]
backtrace: Option<Backtrace>,
},
}
#[derive(Error, Debug)]
pub enum ArcBacktrace {
#[error("...")]
Test {
#[backtrace]
backtrace: Arc<Backtrace>,
},
}
#[derive(Error, Debug)]
pub enum BacktraceFrom {
#[error("...")]
Test {
#[from]
source: Inner,
#[backtrace]
backtrace: Backtrace,
},
}
#[derive(Error, Debug)]
pub enum CombinedBacktraceFrom {
#[error("...")]
Test {
#[from]
#[backtrace]
source: InnerBacktrace,
},
}
#[derive(Error, Debug)]
pub enum OptBacktraceFrom {
#[error("...")]
Test {
#[from]
source: Inner,
#[backtrace]
backtrace: Option<Backtrace>,
},
}
#[derive(Error, Debug)]
pub enum ArcBacktraceFrom {
#[error("...")]
Test {
#[from]
source: Inner,
#[backtrace]
backtrace: Arc<Backtrace>,
},
}
#[test]
fn test_backtrace() {
let error = PlainBacktrace::Test {
backtrace: Backtrace::capture(),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ExplicitBacktrace::Test {
backtrace: Backtrace::capture(),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = OptBacktrace::Test {
backtrace: Some(Backtrace::capture()),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ArcBacktrace::Test {
backtrace: Arc::new(Backtrace::capture()),
};
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = BacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = CombinedBacktraceFrom::from(InnerBacktrace {
backtrace: Backtrace::capture(),
});
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = OptBacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
let error = ArcBacktraceFrom::from(Inner);
assert!(error::request_ref::<Backtrace>(&error).is_some());
}
}
#[test]
#[cfg_attr(
not(thiserror_nightly_testing),
ignore = "requires `--cfg=thiserror_nightly_testing`"
)]
fn test_backtrace() {}

View File

@@ -1,478 +0,0 @@
#![allow(
clippy::elidable_lifetime_names,
clippy::needless_lifetimes,
clippy::needless_raw_string_hashes,
clippy::trivially_copy_pass_by_ref,
clippy::uninlined_format_args
)]
use core::fmt::{self, Display};
use thiserror::Error;
fn assert<T: Display>(expected: &str, value: T) {
assert_eq!(expected, value.to_string());
}
#[test]
fn test_braced() {
#[derive(Error, Debug)]
#[error("braced error: {msg}")]
struct Error {
msg: String,
}
let msg = "T".to_owned();
assert("braced error: T", Error { msg });
}
#[test]
fn test_braced_unused() {
#[derive(Error, Debug)]
#[error("braced error")]
struct Error {
extra: usize,
}
assert("braced error", Error { extra: 0 });
}
#[test]
fn test_tuple() {
#[derive(Error, Debug)]
#[error("tuple error: {0}")]
struct Error(usize);
assert("tuple error: 0", Error(0));
}
#[test]
fn test_unit() {
#[derive(Error, Debug)]
#[error("unit error")]
struct Error;
assert("unit error", Error);
}
#[test]
fn test_enum() {
#[derive(Error, Debug)]
enum Error {
#[error("braced error: {id}")]
Braced { id: usize },
#[error("tuple error: {0}")]
Tuple(usize),
#[error("unit error")]
Unit,
}
assert("braced error: 0", Error::Braced { id: 0 });
assert("tuple error: 0", Error::Tuple(0));
assert("unit error", Error::Unit);
}
#[test]
fn test_constants() {
#[derive(Error, Debug)]
#[error("{MSG}: {id:?} (code {CODE:?})")]
struct Error {
id: &'static str,
}
const MSG: &str = "failed to do";
const CODE: usize = 9;
assert("failed to do: \"\" (code 9)", Error { id: "" });
}
#[test]
fn test_inherit() {
#[derive(Error, Debug)]
#[error("{0}")]
enum Error {
Some(&'static str),
#[error("other error")]
Other(&'static str),
}
assert("some error", Error::Some("some error"));
assert("other error", Error::Other("..."));
}
#[test]
fn test_brace_escape() {
#[derive(Error, Debug)]
#[error("fn main() {{}}")]
struct Error;
assert("fn main() {}", Error);
}
#[test]
fn test_expr() {
#[derive(Error, Debug)]
#[error("1 + 1 = {}", 1 + 1)]
struct Error;
assert("1 + 1 = 2", Error);
}
#[test]
fn test_nested() {
#[derive(Error, Debug)]
#[error("!bool = {}", not(.0))]
struct Error(bool);
#[allow(clippy::trivially_copy_pass_by_ref)]
fn not(bool: &bool) -> bool {
!*bool
}
assert("!bool = false", Error(true));
}
#[test]
fn test_match() {
#[derive(Error, Debug)]
#[error("{intro}: {0}", intro = match .1 {
Some(n) => format!("error occurred with {}", n),
None => "there was an empty error".to_owned(),
})]
struct Error(String, Option<usize>);
assert(
"error occurred with 1: ...",
Error("...".to_owned(), Some(1)),
);
assert(
"there was an empty error: ...",
Error("...".to_owned(), None),
);
}
#[test]
fn test_nested_display() {
// Same behavior as the one in `test_match`, but without String allocations.
#[derive(Error, Debug)]
#[error("{}", {
struct Msg<'a>(&'a String, &'a Option<usize>);
impl<'a> Display for Msg<'a> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self.1 {
Some(n) => write!(formatter, "error occurred with {}", n),
None => write!(formatter, "there was an empty error"),
}?;
write!(formatter, ": {}", self.0)
}
}
Msg(.0, .1)
})]
struct Error(String, Option<usize>);
assert(
"error occurred with 1: ...",
Error("...".to_owned(), Some(1)),
);
assert(
"there was an empty error: ...",
Error("...".to_owned(), None),
);
}
#[test]
fn test_void() {
#[allow(clippy::empty_enums)]
#[derive(Error, Debug)]
#[error("...")]
pub enum Error {}
let _: Error;
}
#[test]
fn test_mixed() {
#[derive(Error, Debug)]
#[error("a={a} :: b={} :: c={c} :: d={d}", 1, c = 2, d = 3)]
struct Error {
a: usize,
d: usize,
}
assert("a=0 :: b=1 :: c=2 :: d=3", Error { a: 0, d: 0 });
}
#[test]
fn test_ints() {
#[derive(Error, Debug)]
enum Error {
#[error("error {0}")]
Tuple(usize, usize),
#[error("error {0}", '?')]
Struct { v: usize },
}
assert("error 9", Error::Tuple(9, 0));
assert("error ?", Error::Struct { v: 0 });
}
#[test]
fn test_trailing_comma() {
#[derive(Error, Debug)]
#[error(
"error {0}",
)]
#[rustfmt::skip]
struct Error(char);
assert("error ?", Error('?'));
}
#[test]
fn test_field() {
#[derive(Debug)]
struct Inner {
data: usize,
}
#[derive(Error, Debug)]
#[error("{}", .0.data)]
struct Error(Inner);
assert("0", Error(Inner { data: 0 }));
}
#[test]
fn test_nested_tuple_field() {
#[derive(Debug)]
struct Inner(usize);
#[derive(Error, Debug)]
#[error("{}", .0.0)]
struct Error(Inner);
assert("0", Error(Inner(0)));
}
#[test]
fn test_pointer() {
#[derive(Error, Debug)]
#[error("{field:p}")]
pub struct Struct {
field: Box<i32>,
}
let s = Struct {
field: Box::new(-1),
};
assert_eq!(s.to_string(), format!("{:p}", s.field));
}
#[test]
fn test_macro_rules_variant_from_call_site() {
// Regression test for https://github.com/dtolnay/thiserror/issues/86
macro_rules! decl_error {
($variant:ident($value:ident)) => {
#[derive(Error, Debug)]
pub enum Error0 {
#[error("{0:?}")]
$variant($value),
}
#[derive(Error, Debug)]
#[error("{0:?}")]
pub enum Error1 {
$variant($value),
}
};
}
decl_error!(Repro(u8));
assert("0", Error0::Repro(0));
assert("0", Error1::Repro(0));
}
#[test]
fn test_macro_rules_message_from_call_site() {
// Regression test for https://github.com/dtolnay/thiserror/issues/398
macro_rules! decl_error {
($($errors:tt)*) => {
#[derive(Error, Debug)]
pub enum Error {
$($errors)*
}
};
}
decl_error! {
#[error("{0}")]
Unnamed(u8),
#[error("{x}")]
Named { x: u8 },
}
assert("0", Error::Unnamed(0));
assert("0", Error::Named { x: 0 });
}
#[test]
fn test_raw() {
#[derive(Error, Debug)]
#[error("braced raw error: {fn}")]
struct Error {
r#fn: &'static str,
}
assert("braced raw error: T", Error { r#fn: "T" });
}
#[test]
fn test_raw_enum() {
#[derive(Error, Debug)]
enum Error {
#[error("braced raw error: {fn}")]
Braced { r#fn: &'static str },
}
assert("braced raw error: T", Error::Braced { r#fn: "T" });
}
#[test]
fn test_keyword() {
#[derive(Error, Debug)]
#[error("error: {type}", type = 1)]
struct Error;
assert("error: 1", Error);
}
#[test]
fn test_self() {
#[derive(Error, Debug)]
#[error("error: {self:?}")]
struct Error;
assert("error: Error", Error);
}
#[test]
fn test_str_special_chars() {
#[derive(Error, Debug)]
pub enum Error {
#[error("brace left {{")]
BraceLeft,
#[error("brace left 2 \x7B\x7B")]
BraceLeft2,
#[error("brace left 3 \u{7B}\u{7B}")]
BraceLeft3,
#[error("brace right }}")]
BraceRight,
#[error("brace right 2 \x7D\x7D")]
BraceRight2,
#[error("brace right 3 \u{7D}\u{7D}")]
BraceRight3,
#[error(
"new_\
line"
)]
NewLine,
#[error("escape24 \u{78}")]
Escape24,
}
assert("brace left {", Error::BraceLeft);
assert("brace left 2 {", Error::BraceLeft2);
assert("brace left 3 {", Error::BraceLeft3);
assert("brace right }", Error::BraceRight);
assert("brace right 2 }", Error::BraceRight2);
assert("brace right 3 }", Error::BraceRight3);
assert("new_line", Error::NewLine);
assert("escape24 x", Error::Escape24);
}
#[test]
fn test_raw_str() {
#[derive(Error, Debug)]
pub enum Error {
#[error(r#"raw brace left {{"#)]
BraceLeft,
#[error(r#"raw brace left 2 \x7B"#)]
BraceLeft2,
#[error(r#"raw brace right }}"#)]
BraceRight,
#[error(r#"raw brace right 2 \x7D"#)]
BraceRight2,
}
assert(r#"raw brace left {"#, Error::BraceLeft);
assert(r#"raw brace left 2 \x7B"#, Error::BraceLeft2);
assert(r#"raw brace right }"#, Error::BraceRight);
assert(r#"raw brace right 2 \x7D"#, Error::BraceRight2);
}
mod util {
use core::fmt::{self, Octal};
pub fn octal<T: Octal>(value: &T, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "0o{:o}", value)
}
}
#[test]
fn test_fmt_path() {
fn unit(formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("unit=")
}
fn pair(k: &i32, v: &i32, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "pair={k}:{v}")
}
#[derive(Error, Debug)]
pub enum Error {
#[error(fmt = unit)]
Unit,
#[error(fmt = pair)]
Tuple(i32, i32),
#[error(fmt = pair)]
Entry { k: i32, v: i32 },
#[error(fmt = crate::util::octal)]
I16(i16),
#[error(fmt = crate::util::octal::<i32>)]
I32 { n: i32 },
#[error(fmt = core::fmt::Octal::fmt)]
I64(i64),
#[error("...{0}")]
Other(bool),
}
assert("unit=", Error::Unit);
assert("pair=10:0", Error::Tuple(10, 0));
assert("pair=10:0", Error::Entry { k: 10, v: 0 });
assert("0o777", Error::I16(0o777));
assert("0o777", Error::I32 { n: 0o777 });
assert("777", Error::I64(0o777));
assert("...false", Error::Other(false));
}
#[test]
fn test_fmt_path_inherited() {
#[derive(Error, Debug)]
#[error(fmt = crate::util::octal)]
pub enum Error {
I16(i16),
I32 {
n: i32,
},
#[error(fmt = core::fmt::Octal::fmt)]
I64(i64),
#[error("...{0}")]
Other(bool),
}
assert("0o777", Error::I16(0o777));
assert("0o777", Error::I32 { n: 0o777 });
assert("777", Error::I64(0o777));
assert("...false", Error::Other(false));
}

View File

@@ -1,56 +0,0 @@
#![allow(dead_code)]
use core::fmt::{self, Display};
use std::io;
use thiserror::Error;
macro_rules! unimplemented_display {
($ty:ty) => {
impl Display for $ty {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
};
}
#[derive(Error, Debug)]
struct BracedError {
msg: String,
pos: usize,
}
#[derive(Error, Debug)]
struct TupleError(String, usize);
#[derive(Error, Debug)]
struct UnitError;
#[derive(Error, Debug)]
struct WithSource {
#[source]
cause: io::Error,
}
#[derive(Error, Debug)]
struct WithAnyhow {
#[source]
cause: anyhow::Error,
}
#[derive(Error, Debug)]
enum EnumError {
Braced {
#[source]
cause: io::Error,
},
Tuple(#[source] io::Error),
Unit,
}
unimplemented_display!(BracedError);
unimplemented_display!(TupleError);
unimplemented_display!(UnitError);
unimplemented_display!(WithSource);
unimplemented_display!(WithAnyhow);
unimplemented_display!(EnumError);

View File

@@ -1,118 +0,0 @@
#![allow(clippy::iter_cloned_collect, clippy::uninlined_format_args)]
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::PathBuf;
use thiserror::Error;
// Some of the elaborate cases from the rcc codebase, which is a C compiler in
// Rust. https://github.com/jyn514/rcc/blob/0.8.0/src/data/error.rs
#[derive(Error, Debug)]
pub enum CompilerError {
#[error("cannot shift {} by {maximum} or more bits (got {current})", if *.is_left { "left" } else { "right" })]
TooManyShiftBits {
is_left: bool,
maximum: u64,
current: u64,
},
#[error("#error {}", (.0).iter().copied().collect::<Vec<_>>().join(" "))]
User(Vec<&'static str>),
#[error("overflow while parsing {}integer literal",
if let Some(signed) = .is_signed {
if *signed { "signed "} else { "unsigned "}
} else {
""
}
)]
IntegerOverflow { is_signed: Option<bool> },
#[error("overflow while parsing {}integer literal", match .is_signed {
Some(true) => "signed ",
Some(false) => "unsigned ",
None => "",
})]
IntegerOverflow2 { is_signed: Option<bool> },
}
// Examples drawn from Rustup.
#[derive(Error, Debug)]
pub enum RustupError {
#[error(
"toolchain '{name}' does not contain component {component}{}",
.suggestion
.as_ref()
.map_or_else(String::new, |s| format!("; did you mean '{}'?", s)),
)]
UnknownComponent {
name: String,
component: String,
suggestion: Option<String>,
},
}
#[track_caller]
fn assert<T: Display>(expected: &str, value: T) {
assert_eq!(expected, value.to_string());
}
#[test]
fn test_rcc() {
assert(
"cannot shift left by 32 or more bits (got 50)",
CompilerError::TooManyShiftBits {
is_left: true,
maximum: 32,
current: 50,
},
);
assert("#error A B C", CompilerError::User(vec!["A", "B", "C"]));
assert(
"overflow while parsing signed integer literal",
CompilerError::IntegerOverflow {
is_signed: Some(true),
},
);
}
#[test]
fn test_rustup() {
assert(
"toolchain 'nightly' does not contain component clipy; did you mean 'clippy'?",
RustupError::UnknownComponent {
name: "nightly".to_owned(),
component: "clipy".to_owned(),
suggestion: Some("clippy".to_owned()),
},
);
}
// Regression test for https://github.com/dtolnay/thiserror/issues/335
#[cfg(feature = "std")]
#[test]
#[allow(non_snake_case)]
fn test_assoc_type_equality_constraint() {
pub trait Trait<T>: Display {
type A;
}
impl<T> Trait<T> for i32 {
type A = i32;
}
#[derive(Error, Debug)]
#[error("{A} {b}", b = &0 as &dyn Trait<i32, A = i32>)]
pub struct Error {
pub A: PathBuf,
}
assert(
"... 0",
Error {
A: PathBuf::from("..."),
},
);
}

View File

@@ -1,64 +0,0 @@
#![allow(clippy::extra_unused_type_parameters)]
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("...")]
pub struct ErrorStruct {
#[from]
source: io::Error,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct ErrorStructOptional {
#[from]
source: Option<io::Error>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct ErrorTuple(#[from] io::Error);
#[derive(Error, Debug)]
#[error("...")]
pub struct ErrorTupleOptional(#[from] Option<io::Error>);
#[derive(Error, Debug)]
#[error("...")]
pub enum ErrorEnum {
Test {
#[from]
source: io::Error,
},
}
#[derive(Error, Debug)]
#[error("...")]
pub enum ErrorEnumOptional {
Test {
#[from]
source: Option<io::Error>,
},
}
#[derive(Error, Debug)]
#[error("...")]
pub enum Many {
Any(#[from] anyhow::Error),
Io(#[from] io::Error),
}
fn assert_impl<T: From<io::Error>>() {}
#[test]
fn test_from() {
assert_impl::<ErrorStruct>();
assert_impl::<ErrorStructOptional>();
assert_impl::<ErrorTuple>();
assert_impl::<ErrorTupleOptional>();
assert_impl::<ErrorEnum>();
assert_impl::<ErrorEnumOptional>();
assert_impl::<Many>();
}

View File

@@ -1,205 +0,0 @@
#![allow(clippy::needless_late_init, clippy::uninlined_format_args)]
use core::fmt::{self, Debug, Display};
use core::str::FromStr;
use thiserror::Error;
pub struct NoFormat;
#[derive(Debug)]
pub struct DebugOnly;
pub struct DisplayOnly;
impl Display for DisplayOnly {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("display only")
}
}
#[derive(Debug)]
pub struct DebugAndDisplay;
impl Display for DebugAndDisplay {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("debug and display")
}
}
// Should expand to:
//
// impl<E> Display for EnumDebugField<E>
// where
// E: Debug;
//
// impl<E> Error for EnumDebugField<E>
// where
// Self: Debug + Display;
//
#[derive(Error, Debug)]
pub enum EnumDebugGeneric<E> {
#[error("{0:?}")]
FatalError(E),
}
// Should expand to:
//
// impl<E> Display for EnumFromGeneric<E>;
//
// impl<E> Error for EnumFromGeneric<E>
// where
// EnumDebugGeneric<E>: Error + 'static,
// Self: Debug + Display;
//
#[derive(Error, Debug)]
pub enum EnumFromGeneric<E> {
#[error("enum from generic")]
Source(#[from] EnumDebugGeneric<E>),
}
// Should expand to:
//
// impl<HasDisplay, HasDebug, HasNeither> Display
// for EnumCompound<HasDisplay, HasDebug, HasNeither>
// where
// HasDisplay: Display,
// HasDebug: Debug;
//
// impl<HasDisplay, HasDebug, HasNeither> Error
// for EnumCompound<HasDisplay, HasDebug, HasNeither>
// where
// Self: Debug + Display;
//
#[derive(Error)]
pub enum EnumCompound<HasDisplay, HasDebug, HasNeither> {
#[error("{0} {1:?}")]
DisplayDebug(HasDisplay, HasDebug),
#[error("{0}")]
Display(HasDisplay, HasNeither),
#[error("{1:?}")]
Debug(HasNeither, HasDebug),
}
impl<HasDisplay, HasDebug, HasNeither> Debug for EnumCompound<HasDisplay, HasDebug, HasNeither> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("EnumCompound")
}
}
#[test]
fn test_display_enum_compound() {
let mut instance: EnumCompound<DisplayOnly, DebugOnly, NoFormat>;
instance = EnumCompound::DisplayDebug(DisplayOnly, DebugOnly);
assert_eq!(format!("{}", instance), "display only DebugOnly");
instance = EnumCompound::Display(DisplayOnly, NoFormat);
assert_eq!(format!("{}", instance), "display only");
instance = EnumCompound::Debug(NoFormat, DebugOnly);
assert_eq!(format!("{}", instance), "DebugOnly");
}
// Should expand to:
//
// impl<E> Display for EnumTransparentGeneric<E>
// where
// E: Display;
//
// impl<E> Error for EnumTransparentGeneric<E>
// where
// E: Error,
// Self: Debug + Display;
//
#[derive(Error, Debug)]
pub enum EnumTransparentGeneric<E> {
#[error(transparent)]
Other(E),
}
// Should expand to:
//
// impl<E> Display for StructDebugGeneric<E>
// where
// E: Debug;
//
// impl<E> Error for StructDebugGeneric<E>
// where
// Self: Debug + Display;
//
#[derive(Error, Debug)]
#[error("{underlying:?}")]
pub struct StructDebugGeneric<E> {
pub underlying: E,
}
// Should expand to:
//
// impl<E> Error for StructFromGeneric<E>
// where
// StructDebugGeneric<E>: Error + 'static,
// Self: Debug + Display;
//
#[derive(Error, Debug)]
pub struct StructFromGeneric<E> {
#[from]
pub source: StructDebugGeneric<E>,
}
// Should expand to:
//
// impl<E> Display for StructTransparentGeneric<E>
// where
// E: Display;
//
// impl<E> Error for StructTransparentGeneric<E>
// where
// E: Error,
// Self: Debug + Display;
//
#[derive(Error, Debug)]
#[error(transparent)]
pub struct StructTransparentGeneric<E>(pub E);
// Should expand to:
//
// impl<T: FromStr> Display for AssociatedTypeError<T>
// where
// T::Err: Display;
//
// impl<T: FromStr> Error for AssociatedTypeError<T>
// where
// Self: Debug + Display;
//
#[derive(Error, Debug)]
pub enum AssociatedTypeError<T: FromStr> {
#[error("couldn't parse matrix")]
Other,
#[error("couldn't parse entry: {0}")]
EntryParseError(T::Err),
}
// Regression test for https://github.com/dtolnay/thiserror/issues/345
#[test]
fn test_no_bound_on_named_fmt() {
#[derive(Error, Debug)]
#[error("{thing}", thing = "...")]
struct Error<T> {
thing: T,
}
let error = Error { thing: DebugOnly };
assert_eq!(error.to_string(), "...");
}
#[test]
fn test_multiple_bound() {
#[derive(Error, Debug)]
#[error("0x{thing:x} 0x{thing:X}")]
pub struct Error<T> {
thing: T,
}
let error = Error { thing: 0xFFi32 };
assert_eq!(error.to_string(), "0xff 0xFF");
}

View File

@@ -1,98 +0,0 @@
#![allow(clippy::mixed_attributes_style)]
use thiserror::Error;
pub use std::error::Error;
#[test]
fn test_allow_attributes() {
#![deny(clippy::allow_attributes)]
#[derive(Error, Debug)]
#[error("...")]
pub struct MyError(#[from] anyhow::Error);
let _: MyError;
}
#[test]
fn test_unused_qualifications() {
#![deny(unused_qualifications)]
// Expansion of derive(Error) macro can't know whether something like
// std::error::Error is already imported in the caller's scope so it must
// suppress unused_qualifications.
#[derive(Error, Debug)]
#[error("...")]
pub struct MyError;
let _: MyError;
}
#[test]
fn test_needless_lifetimes() {
#![allow(dead_code)]
#![deny(clippy::elidable_lifetime_names, clippy::needless_lifetimes)]
#[derive(Error, Debug)]
#[error("...")]
pub enum MyError<'a> {
A(#[from] std::io::Error),
B(&'a ()),
}
let _: MyError;
}
#[test]
fn test_deprecated() {
#![deny(deprecated)]
#[derive(Error, Debug)]
#[deprecated]
#[error("...")]
pub struct DeprecatedStruct;
#[derive(Error, Debug)]
#[error("{message} {}", .message)]
pub struct DeprecatedStructField {
#[deprecated]
message: String,
}
#[derive(Error, Debug)]
#[deprecated]
pub enum DeprecatedEnum {
#[error("...")]
Variant,
}
#[derive(Error, Debug)]
pub enum DeprecatedVariant {
#[deprecated]
#[error("...")]
Variant,
}
#[derive(Error, Debug)]
pub enum DeprecatedFrom {
#[error(transparent)]
Variant(
#[from]
#[allow(deprecated)]
DeprecatedStruct,
),
}
#[allow(deprecated)]
let _: DeprecatedStruct;
#[allow(deprecated)]
let _: DeprecatedStructField;
#[allow(deprecated)]
let _ = DeprecatedEnum::Variant;
#[allow(deprecated)]
let _ = DeprecatedVariant::Variant;
#[allow(deprecated)]
let _ = DeprecatedFrom::Variant(DeprecatedStruct);
}

View File

@@ -1,109 +0,0 @@
#![cfg(feature = "std")]
#![cfg_attr(thiserror_nightly_testing, feature(error_generic_member_access))]
#[cfg(thiserror_nightly_testing)]
pub mod structs {
use std::backtrace::Backtrace;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("...")]
pub struct OptSourceNoBacktrace {
#[source]
pub source: Option<anyhow::Error>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct OptSourceAlwaysBacktrace {
#[source]
pub source: Option<anyhow::Error>,
pub backtrace: Backtrace,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct NoSourceOptBacktrace {
#[backtrace]
pub backtrace: Option<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct AlwaysSourceOptBacktrace {
pub source: anyhow::Error,
#[backtrace]
pub backtrace: Option<Backtrace>,
}
#[derive(Error, Debug)]
#[error("...")]
pub struct OptSourceOptBacktrace {
#[source]
pub source: Option<anyhow::Error>,
#[backtrace]
pub backtrace: Option<Backtrace>,
}
}
#[cfg(thiserror_nightly_testing)]
pub mod enums {
use std::backtrace::Backtrace;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum OptSourceNoBacktrace {
#[error("...")]
Test {
#[source]
source: Option<anyhow::Error>,
},
}
#[derive(Error, Debug)]
pub enum OptSourceAlwaysBacktrace {
#[error("...")]
Test {
#[source]
source: Option<anyhow::Error>,
backtrace: Backtrace,
},
}
#[derive(Error, Debug)]
pub enum NoSourceOptBacktrace {
#[error("...")]
Test {
#[backtrace]
backtrace: Option<Backtrace>,
},
}
#[derive(Error, Debug)]
pub enum AlwaysSourceOptBacktrace {
#[error("...")]
Test {
source: anyhow::Error,
#[backtrace]
backtrace: Option<Backtrace>,
},
}
#[derive(Error, Debug)]
pub enum OptSourceOptBacktrace {
#[error("...")]
Test {
#[source]
source: Option<anyhow::Error>,
#[backtrace]
backtrace: Option<Backtrace>,
},
}
}
#[test]
#[cfg_attr(
not(thiserror_nightly_testing),
ignore = "requires `--cfg=thiserror_nightly_testing`"
)]
fn test_option() {}

View File

@@ -1,54 +0,0 @@
#![cfg(feature = "std")]
use core::fmt::Display;
use ref_cast::RefCast;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Error, Debug)]
#[error("failed to read '{file}'")]
struct StructPathBuf {
file: PathBuf,
}
#[derive(Error, Debug, RefCast)]
#[repr(C)]
#[error("failed to read '{file}'")]
struct StructPath {
file: Path,
}
#[derive(Error, Debug)]
enum EnumPathBuf {
#[error("failed to read '{0}'")]
Read(PathBuf),
}
#[derive(Error, Debug)]
#[error("{tail}")]
pub struct UnsizedError {
pub head: i32,
pub tail: str,
}
#[derive(Error, Debug)]
pub enum BothError {
#[error("display:{0} debug:{0:?}")]
DisplayDebug(PathBuf),
#[error("debug:{0:?} display:{0}")]
DebugDisplay(PathBuf),
}
fn assert<T: Display>(expected: &str, value: T) {
assert_eq!(expected, value.to_string());
}
#[test]
fn test_display() {
let path = Path::new("/thiserror");
let file = path.to_owned();
assert("failed to read '/thiserror'", StructPathBuf { file });
let file = path.to_owned();
assert("failed to read '/thiserror'", EnumPathBuf::Read(file));
assert("failed to read '/thiserror'", StructPath::ref_cast(path));
}

View File

@@ -1,82 +0,0 @@
use std::error::Error as StdError;
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("implicit source")]
pub struct ImplicitSource {
source: io::Error,
}
#[derive(Error, Debug)]
#[error("explicit source")]
pub struct ExplicitSource {
source: String,
#[source]
io: io::Error,
}
#[derive(Error, Debug)]
#[error("boxed source")]
pub struct BoxedSource {
#[source]
source: Box<dyn StdError + Send + 'static>,
}
#[test]
fn test_implicit_source() {
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
let error = ImplicitSource { source: io };
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
}
#[test]
fn test_explicit_source() {
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
let error = ExplicitSource {
source: String::new(),
io,
};
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
}
#[test]
fn test_boxed_source() {
let source = Box::new(io::Error::new(io::ErrorKind::Other, "oh no!"));
let error = BoxedSource { source };
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
}
macro_rules! error_from_macro {
($($variants:tt)*) => {
#[derive(Error)]
#[derive(Debug)]
pub enum MacroSource {
$($variants)*
}
}
}
// Test that we generate impls with the proper hygiene
#[rustfmt::skip]
error_from_macro! {
#[error("Something")]
Variant(#[from] io::Error)
}
#[test]
fn test_not_source() {
#[derive(Error, Debug)]
#[error("{source} ==> {destination}")]
pub struct NotSource {
r#source: char,
destination: char,
}
let error = NotSource {
source: 'S',
destination: 'D',
};
assert_eq!(error.to_string(), "S ==> D");
assert!(error.source().is_none());
}

View File

@@ -1,96 +0,0 @@
use anyhow::anyhow;
use std::error::Error as _;
use std::io;
use thiserror::Error;
#[test]
fn test_transparent_struct() {
#[derive(Error, Debug)]
#[error(transparent)]
struct Error(ErrorKind);
#[derive(Error, Debug)]
enum ErrorKind {
#[error("E0")]
E0,
#[error("E1")]
E1(#[from] io::Error),
}
let error = Error(ErrorKind::E0);
assert_eq!("E0", error.to_string());
assert!(error.source().is_none());
let io = io::Error::new(io::ErrorKind::Other, "oh no!");
let error = Error(ErrorKind::from(io));
assert_eq!("E1", error.to_string());
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
}
#[test]
fn test_transparent_enum() {
#[derive(Error, Debug)]
enum Error {
#[error("this failed")]
This,
#[error(transparent)]
Other(anyhow::Error),
}
let error = Error::This;
assert_eq!("this failed", error.to_string());
let error = Error::Other(anyhow!("inner").context("outer"));
assert_eq!("outer", error.to_string());
assert_eq!("inner", error.source().unwrap().to_string());
}
#[test]
fn test_transparent_enum_with_default_message() {
#[derive(Error, Debug)]
#[error("this failed: {0}_{1}")]
enum Error {
This(i32, i32),
#[error(transparent)]
Other(anyhow::Error),
}
let error = Error::This(-1, -1);
assert_eq!("this failed: -1_-1", error.to_string());
let error = Error::Other(anyhow!("inner").context("outer"));
assert_eq!("outer", error.to_string());
assert_eq!("inner", error.source().unwrap().to_string());
}
#[test]
fn test_anyhow() {
#[derive(Error, Debug)]
#[error(transparent)]
struct Any(#[from] anyhow::Error);
let error = Any::from(anyhow!("inner").context("outer"));
assert_eq!("outer", error.to_string());
assert_eq!("inner", error.source().unwrap().to_string());
}
#[test]
fn test_non_static() {
#[derive(Error, Debug)]
#[error(transparent)]
struct Error<'a> {
inner: ErrorKind<'a>,
}
#[derive(Error, Debug)]
enum ErrorKind<'a> {
#[error("unexpected token: {:?}", token)]
Unexpected { token: &'a str },
}
let error = Error {
inner: ErrorKind::Unexpected { token: "error" },
};
assert_eq!("unexpected token: \"error\"", error.to_string());
assert!(error.source().is_none());
}

View File

@@ -1,7 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
pub struct Error(#[error(transparent)] std::io::Error);
fn main() {}

View File

@@ -1,5 +0,0 @@
error: #[error(transparent)] needs to go outside the enum or struct, not on an individual field
--> tests/ui/bad-field-attr.rs:5:18
|
5 | pub struct Error(#[error(transparent)] std::io::Error);
| ^^^^^^^^^^^^^^^^^^^^^

View File

@@ -1,15 +0,0 @@
use thiserror::Error;
macro_rules! error_type {
($name:ident, $what:expr) => {
// Use #[error("invalid {}", $what)] instead.
#[derive(Error, Debug)]
#[error(concat!("invalid ", $what))]
pub struct $name;
};
}
error_type!(Error, "foo");
fn main() {}

View File

@@ -1,10 +0,0 @@
error: expected one of: string literal, `transparent`, `fmt`
--> tests/ui/concat-display.rs:8:17
|
8 | #[error(concat!("invalid ", $what))]
| ^^^^^^
...
13 | error_type!(Error, "foo");
| ------------------------- in this macro invocation
|
= note: this error originates in the macro `error_type` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -1,7 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{_}")]
pub struct Error;
fn main() {}

View File

@@ -1,7 +0,0 @@
error: invalid format string: invalid argument name `_`
--> tests/ui/display-underscore.rs:4:11
|
4 | #[error("{_}")]
| ^ invalid argument name in format string
|
= note: argument name cannot be a single underscore

View File

@@ -1,13 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ErrorEnum {
Confusing {
#[source]
a: std::io::Error,
#[source]
b: anyhow::Error,
},
}
fn main() {}

View File

@@ -1,5 +0,0 @@
error: duplicate #[source] attribute
--> tests/ui/duplicate-enum-source.rs:8:9
|
8 | #[source]
| ^^^^^^^^^

View File

@@ -1,23 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error("...")]
#[error("...")]
pub struct Error;
#[derive(Error, Debug)]
#[error(fmt = core::fmt::Octal::fmt)]
#[error(fmt = core::fmt::LowerHex::fmt)]
pub enum FmtFmt {}
#[derive(Error, Debug)]
#[error(fmt = core::fmt::Octal::fmt)]
#[error(transparent)]
pub enum FmtTransparent {}
#[derive(Error, Debug)]
#[error(fmt = core::fmt::Octal::fmt)]
#[error("...")]
pub enum FmtDisplay {}
fn main() {}

View File

@@ -1,23 +0,0 @@
error: only one #[error(...)] attribute is allowed
--> tests/ui/duplicate-fmt.rs:5:1
|
5 | #[error("...")]
| ^^^^^^^^^^^^^^^
error: duplicate #[error(fmt = ...)] attribute
--> tests/ui/duplicate-fmt.rs:10:1
|
10 | #[error(fmt = core::fmt::LowerHex::fmt)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: cannot have both #[error(transparent)] and #[error(fmt = ...)]
--> tests/ui/duplicate-fmt.rs:14:1
|
14 | #[error(fmt = core::fmt::Octal::fmt)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: cannot have both #[error(fmt = ...)] and a format arguments attribute
--> tests/ui/duplicate-fmt.rs:20:1
|
20 | #[error("...")]
| ^^^^^^^^^^^^^^^

View File

@@ -1,11 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub struct ErrorStruct {
#[source]
a: std::io::Error,
#[source]
b: anyhow::Error,
}
fn main() {}

View File

@@ -1,5 +0,0 @@
error: duplicate #[source] attribute
--> tests/ui/duplicate-struct-source.rs:7:5
|
7 | #[source]
| ^^^^^^^^^

View File

@@ -1,8 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
#[error(transparent)]
pub struct Error(anyhow::Error);
fn main() {}

View File

@@ -1,5 +0,0 @@
error: duplicate #[error(transparent)] attribute
--> tests/ui/duplicate-transparent.rs:5:1
|
5 | #[error(transparent)]
| ^^^^^^^^^^^^^^^^^^^^^

View File

@@ -1,7 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error("".yellow)]
pub struct ArgError;
fn main() {}

View File

@@ -1,24 +0,0 @@
error: expected `,`, found `.`
--> tests/ui/expression-fallback.rs:4:11
|
4 | #[error("".yellow)]
| ^ expected `,`
error: argument never used
--> tests/ui/expression-fallback.rs:4:12
|
4 | #[error("".yellow)]
| -- ^^^^^^ argument never used
| |
| formatting specifier missing
|
help: format specifiers use curly braces, consider adding a format specifier
|
4 | #[error("{}".yellow)]
| ++
error[E0425]: cannot find value `yellow` in this scope
--> tests/ui/expression-fallback.rs:4:12
|
4 | #[error("".yellow)]
| ^^^^^^ not found in this scope

View File

@@ -1,14 +0,0 @@
use core::fmt::{self, Display};
use thiserror::Error;
#[derive(Error, Debug)]
#[error]
pub struct MyError;
impl Display for MyError {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}
fn main() {}

View File

@@ -1,16 +0,0 @@
error: expected attribute arguments in parentheses: #[error(...)]
--> tests/ui/fallback-impl-with-display.rs:5:3
|
5 | #[error]
| ^^^^^
error[E0119]: conflicting implementations of trait `std::fmt::Display` for type `MyError`
--> tests/ui/fallback-impl-with-display.rs:4:10
|
4 | #[derive(Error, Debug)]
| ^^^^^ conflicting implementation for `MyError`
...
8 | impl Display for MyError {
| ------------------------ first implementation here
|
= note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -1,15 +0,0 @@
// https://github.com/dtolnay/thiserror/issues/163
use std::backtrace::Backtrace;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("...")]
pub struct Error(
#[from]
#[backtrace]
std::io::Error,
Backtrace,
);
fn main() {}

View File

@@ -1,5 +0,0 @@
error: deriving From requires no fields other than source and backtrace
--> tests/ui/from-backtrace-backtrace.rs:9:5
|
9 | #[from]
| ^^^^^^^

View File

@@ -1,11 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub struct Error {
#[source]
source: std::io::Error,
#[from]
other: anyhow::Error,
}
fn main() {}

View File

@@ -1,5 +0,0 @@
error: #[from] is only supported on the source field, not any other field
--> tests/ui/from-not-source.rs:7:5
|
7 | #[from]
| ^^^^^^^

View File

@@ -1,11 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error]
pub struct MyError;
fn main() {
// No error on the following line. Thiserror emits an Error impl despite the
// bad attribute.
_ = &MyError as &dyn std::error::Error;
}

View File

@@ -1,5 +0,0 @@
error: expected attribute arguments in parentheses: #[error(...)]
--> tests/ui/invalid-input-impl-anyway.rs:4:3
|
4 | #[error]
| ^^^^^

View File

@@ -1,24 +0,0 @@
use core::fmt::Debug;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("error")]
struct Error<'a>(#[from] Inner<'a>);
#[derive(Error, Debug)]
#[error("{0}")]
struct Inner<'a>(&'a str);
#[derive(Error, Debug)]
enum Enum<'a> {
#[error("error")]
Foo(#[from] Generic<&'a str>),
}
#[derive(Error, Debug)]
#[error("{0:?}")]
struct Generic<T: Debug>(T);
fn main() -> Result<(), Error<'static>> {
Err(Error(Inner("some text")))
}

View File

@@ -1,11 +0,0 @@
error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static
--> tests/ui/lifetime.rs:6:26
|
6 | struct Error<'a>(#[from] Inner<'a>);
| ^^^^^^^^^
error: non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static
--> tests/ui/lifetime.rs:15:17
|
15 | Foo(#[from] Generic<&'a str>),
| ^^^^^^^^^^^^^^^^

View File

@@ -1,9 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
First,
Second,
}
fn main() {}

View File

@@ -1,19 +0,0 @@
error[E0277]: `MyError` doesn't implement `std::fmt::Display`
--> tests/ui/missing-display.rs:4:10
|
3 | #[derive(Error, Debug)]
| ----- in this derive macro expansion
4 | pub enum MyError {
| ^^^^^^^ unsatisfied trait bound
|
help: the trait `std::fmt::Display` is not implemented for `MyError`
--> tests/ui/missing-display.rs:4:1
|
4 | pub enum MyError {
| ^^^^^^^^^^^^^^^^
note: required by a bound in `std::error::Error`
--> $RUST/core/src/error.rs
|
| pub trait Error: Debug + Display {
| ^^^^^^^ required by this bound in `Error`
= note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -1,10 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("...")]
A(usize),
B(usize),
}
fn main() {}

Some files were not shown because too many files have changed in this diff Show More