Enable suppression of magic values by type (#1987)

Closes #1949.
This commit is contained in:
Charlie Marsh 2023-01-18 20:44:24 -05:00 committed by GitHub
parent 34412a0a01
commit d33424ec9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 287 additions and 62 deletions

View File

@ -3407,6 +3407,25 @@ convention = "google"
--- ---
### `pylint`
#### [`allow-magic-value-types`](#allow-magic-value-types)
Constant types to ignore when used as "magic values".
**Default value**: `["str"]`
**Type**: `Vec<ConstantType>`
**Example usage**:
```toml
[tool.ruff.pylint]
allow-magic-value-types = ["int"]
```
---
### `pyupgrade` ### `pyupgrade`
#### [`keep-runtime-typing`](#keep-runtime-typing) #### [`keep-runtime-typing`](#keep-runtime-typing)

View File

@ -1,5 +1,4 @@
"""Check that magic values are not used in comparisons""" """Check that magic values are not used in comparisons."""
import cmath
user_input = 10 user_input = 10
@ -68,4 +67,3 @@ if user_input == b"something": # [magic-value-comparison]
if user_input == HELLO_WORLD: # correct if user_input == HELLO_WORLD: # correct
pass pass

View File

@ -341,6 +341,17 @@
} }
] ]
}, },
"pylint": {
"description": "Options for the `pylint` plugin.",
"anyOf": [
{
"$ref": "#/definitions/PylintOptions"
},
{
"type": "null"
}
]
},
"pyupgrade": { "pyupgrade": {
"description": "Options for the `pyupgrade` plugin.", "description": "Options for the `pyupgrade` plugin.",
"anyOf": [ "anyOf": [
@ -461,6 +472,20 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"ConstantType": {
"type": "string",
"enum": [
"bool",
"bytes",
"complex",
"ellipsis",
"float",
"int",
"none",
"str",
"tuple"
]
},
"Convention": { "Convention": {
"oneOf": [ "oneOf": [
{ {
@ -1046,6 +1071,22 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"PylintOptions": {
"type": "object",
"properties": {
"allow-magic-value-types": {
"description": "Constant types to ignore when used as \"magic values\".",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/ConstantType"
}
}
},
"additionalProperties": false
},
"PythonVersion": { "PythonVersion": {
"type": "string", "type": "string",
"enum": [ "enum": [

View File

@ -454,6 +454,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -520,6 +521,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -586,6 +588,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -652,6 +655,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -723,6 +727,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -795,6 +800,7 @@ mod tests {
pydocstyle: Some(pydocstyle::settings::Options { pydocstyle: Some(pydocstyle::settings::Options {
convention: Some(Convention::Numpy), convention: Some(Convention::Numpy),
}), }),
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);
@ -867,6 +873,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}); });
assert_eq!(actual, expected); assert_eq!(actual, expected);

View File

@ -1,5 +1,6 @@
//! Rules from [Pylint](https://pypi.org/project/pylint/2.15.7/). //! Rules from [Pylint](https://pypi.org/project/pylint/2.15.7/).
pub(crate) mod rules; pub(crate) mod rules;
pub mod settings;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -10,6 +11,7 @@ mod tests {
use crate::linter::test_path; use crate::linter::test_path;
use crate::registry::RuleCode; use crate::registry::RuleCode;
use crate::rules::pylint;
use crate::settings::Settings; use crate::settings::Settings;
#[test_case(RuleCode::PLC0414, Path::new("import_aliasing.py"); "PLC0414")] #[test_case(RuleCode::PLC0414, Path::new("import_aliasing.py"); "PLC0414")]
@ -42,4 +44,19 @@ mod tests {
insta::assert_yaml_snapshot!(snapshot, diagnostics); insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test]
fn allow_magic_value_types() -> Result<()> {
let diagnostics = test_path(
Path::new("./resources/test/fixtures/pylint/magic_value_comparison.py"),
&Settings {
pylint: pylint::settings::Settings {
allow_magic_value_types: vec![pylint::settings::ConstantType::Int],
},
..Settings::for_rules(vec![RuleCode::PLR2004])
},
)?;
insta::assert_yaml_snapshot!(diagnostics);
Ok(())
}
} }

View File

@ -4,20 +4,25 @@ use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Diagnostic; use crate::registry::Diagnostic;
use crate::rules::pylint::settings::ConstantType;
use crate::violations; use crate::violations;
fn is_magic_value(constant: &Constant) -> bool { fn is_magic_value(constant: &Constant, allowed_types: &[ConstantType]) -> bool {
if allowed_types.contains(&constant.into()) {
return false;
}
match constant { match constant {
// Ignore `None`, `Bool`, and `Ellipsis` constants.
Constant::None => false, Constant::None => false,
// E712 `if True == do_something():`
Constant::Bool(_) => false, Constant::Bool(_) => false,
Constant::Ellipsis => false,
// Otherwise, special-case some common string and integer types.
Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"), Constant::Str(value) => !matches!(value.as_str(), "" | "__main__"),
Constant::Bytes(_) => true,
Constant::Int(value) => !matches!(value.try_into(), Ok(-1 | 0 | 1)), Constant::Int(value) => !matches!(value.try_into(), Ok(-1 | 0 | 1)),
Constant::Bytes(_) => true,
Constant::Tuple(_) => true, Constant::Tuple(_) => true,
Constant::Float(_) => true, Constant::Float(_) => true,
Constant::Complex { .. } => true, Constant::Complex { .. } => true,
Constant::Ellipsis => true,
} }
} }
@ -38,13 +43,13 @@ pub fn magic_value_comparison(checker: &mut Checker, left: &Expr, comparators: &
for comparison_expr in std::iter::once(left).chain(comparators.iter()) { for comparison_expr in std::iter::once(left).chain(comparators.iter()) {
if let ExprKind::Constant { value, .. } = &comparison_expr.node { if let ExprKind::Constant { value, .. } = &comparison_expr.node {
if is_magic_value(value) { if is_magic_value(value, &checker.settings.pylint.allow_magic_value_types) {
let diagnostic = Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
violations::MagicValueComparison(value.to_string()), violations::MagicValueComparison {
value: value.to_string(),
},
Range::from_located(comparison_expr), Range::from_located(comparison_expr),
); ));
checker.diagnostics.push(diagnostic);
} }
} }
} }

View File

@ -0,0 +1,87 @@
//! Settings for the `pylint` plugin.
use ruff_macros::ConfigurationOptions;
use rustpython_ast::Constant;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum ConstantType {
Bool,
Bytes,
Complex,
Ellipsis,
Float,
Int,
None,
Str,
Tuple,
}
impl From<&Constant> for ConstantType {
fn from(value: &Constant) -> Self {
match value {
Constant::Bool(..) => Self::Bool,
Constant::Bytes(..) => Self::Bytes,
Constant::Complex { .. } => Self::Complex,
Constant::Ellipsis => Self::Ellipsis,
Constant::Float(..) => Self::Float,
Constant::Int(..) => Self::Int,
Constant::None => Self::None,
Constant::Str(..) => Self::Str,
Constant::Tuple(..) => Self::Tuple,
}
}
}
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "PylintOptions"
)]
pub struct Options {
#[option(
default = r#"["str"]"#,
value_type = "Vec<ConstantType>",
example = r#"
allow-magic-value-types = ["int"]
"#
)]
/// Constant types to ignore when used as "magic values".
pub allow_magic_value_types: Option<Vec<ConstantType>>,
}
#[derive(Debug, Hash)]
pub struct Settings {
pub allow_magic_value_types: Vec<ConstantType>,
}
impl Default for Settings {
fn default() -> Self {
Self {
allow_magic_value_types: vec![ConstantType::Str],
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
allow_magic_value_types: options
.allow_magic_value_types
.unwrap_or_else(|| vec![ConstantType::Str]),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
allow_magic_value_types: Some(settings.allow_magic_value_types),
}
}
}

View File

@ -3,52 +3,46 @@ source: src/rules/pylint/mod.rs
expression: diagnostics expression: diagnostics
--- ---
- kind: - kind:
MagicValueComparison: "10" MagicValueComparison:
value: "10"
location: location:
row: 6 row: 5
column: 3 column: 3
end_location: end_location:
row: 6 row: 5
column: 5 column: 5
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
MagicValueComparison: "2" MagicValueComparison:
value: "2"
location: location:
row: 36 row: 35
column: 11 column: 11
end_location: end_location:
row: 36 row: 35
column: 12 column: 12
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
MagicValueComparison: "'Hunter2'" MagicValueComparison:
value: "3.141592653589793"
location: location:
row: 51 row: 56
column: 21
end_location:
row: 51
column: 30
fix: ~
parent: ~
- kind:
MagicValueComparison: "3.141592653589793"
location:
row: 57
column: 20 column: 20
end_location: end_location:
row: 57 row: 56
column: 40 column: 40
fix: ~ fix: ~
parent: ~ parent: ~
- kind: - kind:
MagicValueComparison: "b'something'" MagicValueComparison:
value: "b'something'"
location: location:
row: 66 row: 65
column: 17 column: 17
end_location: end_location:
row: 66 row: 65
column: 29 column: 29
fix: ~ fix: ~
parent: ~ parent: ~

View File

@ -0,0 +1,38 @@
---
source: src/rules/pylint/mod.rs
expression: diagnostics
---
- kind:
MagicValueComparison:
value: "'Hunter2'"
location:
row: 50
column: 21
end_location:
row: 50
column: 30
fix: ~
parent: ~
- kind:
MagicValueComparison:
value: "3.141592653589793"
location:
row: 56
column: 20
end_location:
row: 56
column: 40
fix: ~
parent: ~
- kind:
MagicValueComparison:
value: "b'something'"
location:
row: 65
column: 17
end_location:
row: 65
column: 29
fix: ~
parent: ~

View File

@ -17,7 +17,7 @@ use crate::registry::RuleCodePrefix;
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
use crate::settings::options::Options; use crate::settings::options::Options;
use crate::settings::pyproject::load_options; use crate::settings::pyproject::load_options;
@ -72,6 +72,7 @@ pub struct Configuration {
pub pep8_naming: Option<pep8_naming::settings::Options>, pub pep8_naming: Option<pep8_naming::settings::Options>,
pub pycodestyle: Option<pycodestyle::settings::Options>, pub pycodestyle: Option<pycodestyle::settings::Options>,
pub pydocstyle: Option<pydocstyle::settings::Options>, pub pydocstyle: Option<pydocstyle::settings::Options>,
pub pylint: Option<pylint::settings::Options>,
pub pyupgrade: Option<pyupgrade::settings::Options>, pub pyupgrade: Option<pyupgrade::settings::Options>,
} }
@ -178,6 +179,7 @@ impl Configuration {
pep8_naming: options.pep8_naming, pep8_naming: options.pep8_naming,
pycodestyle: options.pycodestyle, pycodestyle: options.pycodestyle,
pydocstyle: options.pydocstyle, pydocstyle: options.pydocstyle,
pylint: options.pylint,
pyupgrade: options.pyupgrade, pyupgrade: options.pyupgrade,
}) })
} }
@ -248,6 +250,7 @@ impl Configuration {
pep8_naming: self.pep8_naming.or(config.pep8_naming), pep8_naming: self.pep8_naming.or(config.pep8_naming),
pycodestyle: self.pycodestyle.or(config.pycodestyle), pycodestyle: self.pycodestyle.or(config.pycodestyle),
pydocstyle: self.pydocstyle.or(config.pydocstyle), pydocstyle: self.pydocstyle.or(config.pydocstyle),
pylint: self.pylint.or(config.pylint),
pyupgrade: self.pyupgrade.or(config.pyupgrade), pyupgrade: self.pyupgrade.or(config.pyupgrade),
} }
} }

View File

@ -10,7 +10,7 @@ use crate::registry::RuleCodePrefix;
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
pub const PREFIXES: &[RuleCodePrefix] = &[RuleCodePrefix::E, RuleCodePrefix::F]; pub const PREFIXES: &[RuleCodePrefix] = &[RuleCodePrefix::E, RuleCodePrefix::F];
@ -84,6 +84,7 @@ impl Default for Settings {
pep8_naming: pep8_naming::settings::Settings::default(), pep8_naming: pep8_naming::settings::Settings::default(),
pycodestyle: pycodestyle::settings::Settings::default(), pycodestyle: pycodestyle::settings::Settings::default(),
pydocstyle: pydocstyle::settings::Settings::default(), pydocstyle: pydocstyle::settings::Settings::default(),
pylint: pylint::settings::Settings::default(),
pyupgrade: pyupgrade::settings::Settings::default(), pyupgrade: pyupgrade::settings::Settings::default(),
} }
} }

View File

@ -12,7 +12,7 @@ use super::types::FilePattern;
pub struct HashableRegex(Regex); pub struct HashableRegex(Regex);
impl Hash for HashableRegex { impl Hash for HashableRegex {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_str().hash(state); self.0.as_str().hash(state);
} }
} }
@ -49,7 +49,7 @@ impl Deref for HashableGlobMatcher {
} }
impl Hash for HashableGlobMatcher { impl Hash for HashableGlobMatcher {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.0.glob().hash(state); self.0.glob().hash(state);
} }
} }
@ -87,7 +87,7 @@ impl Deref for HashableGlobSet {
} }
impl Hash for HashableGlobSet { impl Hash for HashableGlobSet {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
for pattern in self.patterns.iter().sorted() { for pattern in self.patterns.iter().sorted() {
pattern.hash(state); pattern.hash(state);
} }

View File

@ -18,7 +18,7 @@ use crate::registry::{RuleCode, RuleCodePrefix, SuffixLength, CATEGORIES, INCOMP
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
use crate::settings::configuration::Configuration; use crate::settings::configuration::Configuration;
use crate::settings::types::{PerFileIgnore, PythonVersion, SerializationFormat, Version}; use crate::settings::types::{PerFileIgnore, PythonVersion, SerializationFormat, Version};
@ -117,6 +117,7 @@ pub struct Settings {
pub pep8_naming: pep8_naming::settings::Settings, pub pep8_naming: pep8_naming::settings::Settings,
pub pycodestyle: pycodestyle::settings::Settings, pub pycodestyle: pycodestyle::settings::Settings,
pub pydocstyle: pydocstyle::settings::Settings, pub pydocstyle: pydocstyle::settings::Settings,
pub pylint: pylint::settings::Settings,
pub pyupgrade: pyupgrade::settings::Settings, pub pyupgrade: pyupgrade::settings::Settings,
} }
@ -200,6 +201,7 @@ impl Settings {
pep8_naming: config.pep8_naming.map(Into::into).unwrap_or_default(), pep8_naming: config.pep8_naming.map(Into::into).unwrap_or_default(),
pycodestyle: config.pycodestyle.map(Into::into).unwrap_or_default(), pycodestyle: config.pycodestyle.map(Into::into).unwrap_or_default(),
pydocstyle: config.pydocstyle.map(Into::into).unwrap_or_default(), pydocstyle: config.pydocstyle.map(Into::into).unwrap_or_default(),
pylint: config.pylint.map(Into::into).unwrap_or_default(),
pyupgrade: config.pyupgrade.map(Into::into).unwrap_or_default(), pyupgrade: config.pyupgrade.map(Into::into).unwrap_or_default(),
}) })
} }

View File

@ -9,7 +9,7 @@ use crate::registry::RuleCodePrefix;
use crate::rules::{ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
use crate::settings::types::{PythonVersion, SerializationFormat, Version}; use crate::settings::types::{PythonVersion, SerializationFormat, Version};
@ -462,6 +462,9 @@ pub struct Options {
/// Options for the `pydocstyle` plugin. /// Options for the `pydocstyle` plugin.
pub pydocstyle: Option<pydocstyle::settings::Options>, pub pydocstyle: Option<pydocstyle::settings::Options>,
#[option_group] #[option_group]
/// Options for the `pylint` plugin.
pub pylint: Option<pylint::settings::Options>,
#[option_group]
/// Options for the `pyupgrade` plugin. /// Options for the `pyupgrade` plugin.
pub pyupgrade: Option<pyupgrade::settings::Options>, pub pyupgrade: Option<pyupgrade::settings::Options>,
// Tables are required to go last. // Tables are required to go last.

View File

@ -208,6 +208,7 @@ mod tests {
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}) })
}) })
@ -268,6 +269,7 @@ line-length = 79
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}) })
}) })
@ -328,6 +330,7 @@ exclude = ["foo.py"]
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}) })
}) })
@ -388,6 +391,7 @@ select = ["E501"]
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}) })
}) })
@ -449,6 +453,7 @@ ignore = ["E501"]
pep8_naming: None, pep8_naming: None,
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
}) })
}) })
@ -630,6 +635,7 @@ other-attribute = 1
}), }),
pycodestyle: None, pycodestyle: None,
pydocstyle: None, pydocstyle: None,
pylint: None,
pyupgrade: None, pyupgrade: None,
} }
); );

View File

@ -1251,18 +1251,22 @@ impl AlwaysAutofixableViolation for UseSysExit {
} }
define_violation!( define_violation!(
pub struct MagicValueComparison(pub String); pub struct MagicValueComparison {
pub value: String,
}
); );
impl Violation for MagicValueComparison { impl Violation for MagicValueComparison {
fn message(&self) -> String { fn message(&self) -> String {
let MagicValueComparison(value) = self; let MagicValueComparison { value } = self;
format!( format!(
"Magic value used in comparison, consider replacing {value} with a constant variable" "Magic value used in comparison, consider replacing {value} with a constant variable"
) )
} }
fn placeholder() -> Self { fn placeholder() -> Self {
MagicValueComparison("magic".to_string()) MagicValueComparison {
value: "magic".to_string(),
}
} }
} }