This commit is contained in:
James Wilson
2025-08-08 11:00:41 +01:00
parent cfbb5eefff
commit 88f31348eb
3 changed files with 67 additions and 39 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ use revive_dt_format::{
corpus::Corpus, corpus::Corpus,
input::Input, input::Input,
metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile}, metadata::{ContractInstance, ContractPathAndIdent, Metadata, MetadataFile},
mode::{Mode, ModePipeline, ModeOptimizerSetting}, mode::{Mode, ModeOptimizerSetting, ModePipeline},
}; };
use revive_dt_node::pool::NodePool; use revive_dt_node::pool::NodePool;
use revive_dt_report::reporter::{Report, Span}; use revive_dt_report::reporter::{Report, Span};
+1 -1
View File
@@ -60,7 +60,7 @@ impl Metadata {
pub fn solc_modes(&self) -> Vec<Mode> { pub fn solc_modes(&self) -> Vec<Mode> {
match &self.modes { match &self.modes {
Some(modes) => Mode::from_parsed_modes(modes.iter()).collect(), Some(modes) => Mode::from_parsed_modes(modes.iter()).collect(),
None => Mode::all().collect() None => Mode::all().collect(),
} }
} }
+65 -37
View File
@@ -1,18 +1,18 @@
use regex::Regex;
use revive_dt_common::types::VersionOrRequirement; use revive_dt_common::types::VersionOrRequirement;
use semver::Version; use semver::Version;
use serde::de::Deserializer; use serde::de::Deserializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::sync::LazyLock;
use std::str::FromStr;
use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
/// This represents a mode that a given test should be run with, if possible. /// This represents a mode that a given test should be run with, if possible.
/// ///
/// We obtain this by taking a [`ParsedMode`], which may be looser or more strict /// We obtain this by taking a [`ParsedMode`], which may be looser or more strict
/// in its requirements, and then expanding it out into a list of [`TestMode`]s. /// in its requirements, and then expanding it out into a list of [`TestMode`]s.
/// ///
/// Use [`ParsedMode::to_test_modes()`] to do this. /// Use [`ParsedMode::to_test_modes()`] to do this.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Mode { pub struct Mode {
@@ -35,10 +35,12 @@ impl Display for Mode {
impl Mode { impl Mode {
/// Return a set of [`TestMode`]s that correspond to the given [`ParsedMode`]s. /// Return a set of [`TestMode`]s that correspond to the given [`ParsedMode`]s.
/// This avoids any duplicate entries. /// This avoids any duplicate entries.
pub fn from_parsed_modes<'a>(parsed: impl Iterator<Item = &'a ParsedMode>) -> impl Iterator<Item = Self> { pub fn from_parsed_modes<'a>(
let modes: HashSet<_> = parsed.flat_map(|p| p.to_test_modes()).collect(); parsed: impl Iterator<Item = &'a ParsedMode>,
modes.into_iter() ) -> impl Iterator<Item = Self> {
let modes: HashSet<_> = parsed.flat_map(|p| p.to_test_modes()).collect();
modes.into_iter()
} }
/// Return all of the test modes that we want to run when no specific [`ParsedMode`] is specified. /// Return all of the test modes that we want to run when no specific [`ParsedMode`] is specified.
@@ -63,13 +65,13 @@ impl Mode {
} }
/// This represents a mode that has been parsed from test metadata. /// This represents a mode that has been parsed from test metadata.
/// ///
/// Mode strings can take the following form (in pseudo-regex): /// Mode strings can take the following form (in pseudo-regex):
/// ///
/// ```text /// ```text
/// [YEILV][+-]? (M[0123sz])? <semver>? /// [YEILV][+-]? (M[0123sz])? <semver>?
/// ``` /// ```
/// ///
/// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`]. /// We can parse valid mode strings into [`ParsedMode`] using [`ParsedMode::from_str`].
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ParsedMode { pub struct ParsedMode {
@@ -93,7 +95,7 @@ impl FromStr for ParsedMode {
$ $
").unwrap() ").unwrap()
}); });
let Some(caps) = REGEX.captures(s) else { let Some(caps) = REGEX.captures(s) else {
return Err(ParseModeError::CannotParse); return Err(ParseModeError::CannotParse);
}; };
@@ -114,7 +116,10 @@ impl FromStr for ParsedMode {
}; };
let version = match caps.name("version") { let version = match caps.name("version") {
Some(m) => Some(semver::VersionReq::parse(m.as_str()).map_err(|e| ParseModeError::InvalidVersion(e.to_string()))?), Some(m) => Some(
semver::VersionReq::parse(m.as_str())
.map_err(|e| ParseModeError::InvalidVersion(e.to_string()))?,
),
None => None, None => None,
}; };
@@ -160,11 +165,11 @@ impl Display for ParsedMode {
} }
fn fmt_mode_parts( fn fmt_mode_parts(
pipeline: Option<&ModePipeline>, pipeline: Option<&ModePipeline>,
optimize_flag: Option<bool>, optimize_flag: Option<bool>,
optimize_setting: Option<&ModeOptimizerSetting>, optimize_setting: Option<&ModeOptimizerSetting>,
version: Option<&semver::VersionReq>, version: Option<&semver::VersionReq>,
f: &mut std::fmt::Formatter<'_> f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result { ) -> std::fmt::Result {
let mut has_written = false; let mut has_written = false;
@@ -177,13 +182,17 @@ fn fmt_mode_parts(
} }
if let Some(optimize_setting) = optimize_setting { if let Some(optimize_setting) = optimize_setting {
if has_written { f.write_str(" ")?; } if has_written {
f.write_str(" ")?;
}
optimize_setting.fmt(f)?; optimize_setting.fmt(f)?;
has_written = true; has_written = true;
} }
if let Some(version) = version { if let Some(version) = version {
if has_written { f.write_str(" ")?; } if has_written {
f.write_str(" ")?;
}
version.fmt(f)?; version.fmt(f)?;
} }
@@ -199,7 +208,11 @@ impl ParsedMode {
); );
let optimize_flag_setting = self.optimize_flag.map(|b| { let optimize_flag_setting = self.optimize_flag.map(|b| {
if b { ModeOptimizerSetting::M3 } else { ModeOptimizerSetting::M0 } if b {
ModeOptimizerSetting::M3
} else {
ModeOptimizerSetting::M0
}
}); });
let optimize_flag_iter = match optimize_flag_setting { let optimize_flag_iter = match optimize_flag_setting {
@@ -213,20 +226,22 @@ impl ParsedMode {
); );
pipeline_iter.flat_map(move |pipeline| { pipeline_iter.flat_map(move |pipeline| {
optimize_settings_iter.clone().map(move |optimize_setting| { optimize_settings_iter
Mode { .clone()
.map(move |optimize_setting| Mode {
pipeline, pipeline,
optimize_setting, optimize_setting,
version: self.version.clone(), version: self.version.clone(),
} })
})
}) })
} }
} }
#[derive(thiserror::Error, Debug, Clone)] #[derive(thiserror::Error, Debug, Clone)]
pub enum ParseModeError { pub enum ParseModeError {
#[error("Cannot parse compiler mode via regex: expecting something like 'Y', 'E+ <0.8', 'Y Mz =0.8'")] #[error(
"Cannot parse compiler mode via regex: expecting something like 'Y', 'E+ <0.8', 'Y Mz =0.8'"
)]
CannotParse, CannotParse,
#[error("Unsupported compiler pipeline mode: {0}. We support Y and E modes only.")] #[error("Unsupported compiler pipeline mode: {0}. We support Y and E modes only.")]
UnsupportedPipeline(char), UnsupportedPipeline(char),
@@ -254,7 +269,9 @@ impl FromStr for ModePipeline {
// Don't go via Yul IR // Don't go via Yul IR
"E" => Ok(ModePipeline::E), "E" => Ok(ModePipeline::E),
// Anything else that we see isn't a mode at all // Anything else that we see isn't a mode at all
_ => Err(ParseModeError::UnsupportedPipeline(s.chars().next().unwrap_or('?'))) _ => Err(ParseModeError::UnsupportedPipeline(
s.chars().next().unwrap_or('?'),
)),
} }
} }
} }
@@ -302,7 +319,7 @@ impl FromStr for ModeOptimizerSetting {
"M3" => Ok(ModeOptimizerSetting::M3), "M3" => Ok(ModeOptimizerSetting::M3),
"Ms" => Ok(ModeOptimizerSetting::Ms), "Ms" => Ok(ModeOptimizerSetting::Ms),
"Mz" => Ok(ModeOptimizerSetting::Mz), "Mz" => Ok(ModeOptimizerSetting::Mz),
_ => Err(ParseModeError::UnsupportedOptimizerSetting(s.to_owned())) _ => Err(ParseModeError::UnsupportedOptimizerSetting(s.to_owned())),
} }
} }
} }
@@ -338,7 +355,7 @@ impl ModeOptimizerSetting {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum EitherIter<A, B> { enum EitherIter<A, B> {
A(A), A(A),
B(B) B(B),
} }
impl<A, B> Iterator for EitherIter<A, B> impl<A, B> Iterator for EitherIter<A, B>
@@ -394,8 +411,13 @@ mod tests {
]; ];
for (actual, expected) in strings { for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual).expect(format!("Failed to parse mode string '{actual}'").as_str()); let parsed = ParsedMode::from_str(actual)
assert_eq!(expected, parsed.to_string(), "Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"); .expect(format!("Failed to parse mode string '{actual}'").as_str());
assert_eq!(
expected,
parsed.to_string(),
"Mode string '{actual}' did not parse to '{expected}': got '{parsed}'"
);
} }
} }
@@ -408,16 +430,22 @@ mod tests {
("Y+", vec!["Y M3"]), ("Y+", vec!["Y M3"]),
("Y-", vec!["Y M0"]), ("Y-", vec!["Y M0"]),
("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]), ("Y <=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8"]),
("<=0.8", vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"]), (
"<=0.8",
vec!["Y M0 <=0.8", "Y M3 <=0.8", "E M0 <=0.8", "E M3 <=0.8"],
),
]; ];
for (actual, expected) in strings { for (actual, expected) in strings {
let parsed = ParsedMode::from_str(actual).expect(format!("Failed to parse mode string '{actual}'").as_str()); let parsed = ParsedMode::from_str(actual)
.expect(format!("Failed to parse mode string '{actual}'").as_str());
let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect(); let expected_set: HashSet<_> = expected.into_iter().map(|s| s.to_owned()).collect();
let actual_set: HashSet<_> = parsed.to_test_modes().map(|m| m.to_string()).collect(); let actual_set: HashSet<_> = parsed.to_test_modes().map(|m| m.to_string()).collect();
assert_eq!(expected_set, actual_set, "Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"); assert_eq!(
expected_set, actual_set,
"Mode string '{actual}' did not expand to '{expected_set:?}': got '{actual_set:?}'"
);
} }
} }
} }