Skip to content

Commit

Permalink
Merge pull request #34 from PlasmaFAIR/add_precision_category
Browse files Browse the repository at this point in the history
Add precision category
  • Loading branch information
LiamPattinson authored Sep 16, 2024
2 parents 9fbafc1 + 78d064b commit 892bb01
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 115 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum Category {
Typing,
/// Failure to use modules or use them appropriately.
Modules,
/// Best practices for setting floating point precision.
Precision,
/// Check path names, directory structures, etc.
FileSystem,
}
Expand All @@ -41,6 +43,7 @@ impl Category {
"S" => Ok(Self::Style),
"T" => Ok(Self::Typing),
"M" => Ok(Self::Modules),
"P" => Ok(Self::Precision),
"F" => Ok(Self::FileSystem),
_ => {
anyhow::bail!("{} is not a rule category.", s)
Expand All @@ -56,6 +59,7 @@ impl fmt::Display for Category {
Self::Style => "S",
Self::Typing => "T",
Self::Modules => "M",
Self::Precision => "P",
Self::FileSystem => "F",
};
write!(f, "{}", s)
Expand Down
45 changes: 23 additions & 22 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod error;
mod filesystem;
mod modules;
mod precision;
mod style;
mod typing;
use crate::{Category, Code, Rule};
Expand Down Expand Up @@ -37,39 +38,39 @@ pub fn build_rule(code_str: &str) -> anyhow::Result<RuleBox> {
Code {
category: Category::Typing,
number: 1,
} => Ok(Box::new(typing::literal_kinds::LiteralKind {})),
} => Ok(Box::new(typing::implicit_typing::ImplicitTyping {})),
Code {
category: Category::Typing,
number: 2,
} => Ok(Box::new(typing::literal_kinds::LiteralKindSuffix {})),
} => Ok(Box::new(
typing::implicit_typing::InterfaceImplicitTyping {},
)),
Code {
category: Category::Typing,
number: 11,
} => Ok(Box::new(typing::star_kinds::StarKind {})),
number: 3,
} => Ok(Box::new(
typing::implicit_typing::SuperfluousImplicitNone {},
)),
Code {
category: Category::Typing,
number: 21,
} => Ok(Box::new(typing::real_precision::DoublePrecision {})),
number: 11,
} => Ok(Box::new(typing::literal_kinds::LiteralKind {})),
Code {
category: Category::Typing,
number: 22,
} => Ok(Box::new(typing::real_precision::NoRealSuffix {})),
number: 12,
} => Ok(Box::new(typing::literal_kinds::LiteralKindSuffix {})),
Code {
category: Category::Typing,
number: 31,
} => Ok(Box::new(typing::implicit_typing::ImplicitTyping {})),
number: 21,
} => Ok(Box::new(typing::star_kinds::StarKind {})),
Code {
category: Category::Typing,
number: 32,
} => Ok(Box::new(
typing::implicit_typing::InterfaceImplicitTyping {},
)),
category: Category::Precision,
number: 1,
} => Ok(Box::new(precision::kind_suffixes::NoRealSuffix {})),
Code {
category: Category::Typing,
number: 33,
} => Ok(Box::new(
typing::implicit_typing::SuperfluousImplicitNone {},
)),
category: Category::Precision,
number: 11,
} => Ok(Box::new(precision::double_precision::DoublePrecision {})),
Code {
category: Category::Modules,
number: 1,
Expand All @@ -87,8 +88,8 @@ pub fn build_rule(code_str: &str) -> anyhow::Result<RuleBox> {
// Returns the full set of all rules.
pub fn full_ruleset() -> RuleSet {
let all_rules = &[
"E001", "F001", "S001", "S101", "T001", "T002", "T011", "T021", "T022", "T031", "T032",
"T033", "M001", "M011",
"E001", "F001", "S001", "S101", "T001", "T002", "T003", "T011", "T012", "T021", "P001",
"P011", "M001", "M011",
];
RuleSet::from_iter(all_rules.iter().map(|x| x.to_string()))
}
Expand Down
102 changes: 102 additions & 0 deletions src/rules/precision/double_precision.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::parsing::to_text;
use crate::{Method, Rule, Violation};
use tree_sitter::Node;
/// Defines rules to avoid the 'double precision' and 'double complex' types.

// TODO rule to prefer 1.23e4_sp over 1.23e4, and 1.23e4_dp over 1.23d4

fn double_precision_err_msg(dtype: &str) -> Option<String> {
match dtype {
"double precision" => Some(String::from(
"prefer 'real(real64)' to 'double precision' (see 'iso_fortran_env')",
)),
"double complex" => Some(String::from(
"prefer 'complex(real64)' to 'double complex' (see 'iso_fortran_env')",
)),
_ => None,
}
}

fn double_precision(node: &Node, src: &str) -> Option<Violation> {
let txt = to_text(node, src)?.to_lowercase();
if let Some(msg) = double_precision_err_msg(txt.as_str()) {
return Some(Violation::from_node(msg.as_str(), node));
}
None
}

pub struct DoublePrecision {}

impl Rule for DoublePrecision {
fn method(&self) -> Method {
Method::Tree(double_precision)
}

fn explain(&self) -> &str {
"
The 'double precision' type does not guarantee a 64-bit floating point number
as one might expect. It is instead required to be twice the size of a default
'real', which may vary depending on your system and can be modified by compiler
arguments. For portability, it is recommended to use `real(dp)`, with `dp` set
in one of the following ways:
- `use, intrinsic :: iso_fortran_env, only: dp => real64`
- `integer, parameter :: dp = selected_real_kind(15, 307)`
For code that should be compatible with C, you should instead use
`real(c_double)`, which may be found in the intrinsic module `iso_c_binding`.
"
}

fn entrypoints(&self) -> Vec<&str> {
vec!["intrinsic_type"]
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::test_utils::test_tree_method;
use crate::violation;
use textwrap::dedent;

#[test]
fn test_double_precision() -> Result<(), String> {
let source = dedent(
"
double precision function double(x)
double precision, intent(in) :: x
double = 2 * x
end function
subroutine triple(x)
double precision, intent(inout) :: x
x = 3 * x
end subroutine
function complex_mul(x, y)
double precision, intent(in) :: x
double complex, intent(in) :: y
double complex :: complex_mul
complex_mul = x * y
end function
",
);
let expected_violations = [
(2, 1, "double precision"),
(3, 3, "double precision"),
(8, 3, "double precision"),
(13, 3, "double precision"),
(14, 3, "double complex"),
(15, 3, "double complex"),
]
.iter()
.map(|(line, col, kind)| {
let msg = double_precision_err_msg(kind).unwrap();
violation!(&msg, *line, *col)
})
.collect();
test_tree_method(&DoublePrecision {}, source, Some(expected_violations))?;
Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,7 @@ use crate::parsing::to_text;
use crate::{Method, Rule, Violation};
use lazy_regex::regex_is_match;
use tree_sitter::Node;
/// Defines rules that ensure real precision is always explicit and stated in a portable way.

// TODO rule to prefer 1.23e4_sp over 1.23e4, and 1.23e4_dp over 1.23d4

fn double_precision_err_msg(dtype: &str) -> Option<String> {
match dtype {
"double precision" => Some(String::from(
"prefer 'real(real64)' to 'double precision' (see 'iso_fortran_env')",
)),
"double complex" => Some(String::from(
"prefer 'complex(real64)' to 'double complex' (see 'iso_fortran_env')",
)),
_ => None,
}
}

fn double_precision(node: &Node, src: &str) -> Option<Violation> {
let txt = to_text(node, src)?.to_lowercase();
if let Some(msg) = double_precision_err_msg(txt.as_str()) {
return Some(Violation::from_node(msg.as_str(), node));
}
None
}

pub struct DoublePrecision {}

impl Rule for DoublePrecision {
fn method(&self) -> Method {
Method::Tree(double_precision)
}

fn explain(&self) -> &str {
"
The 'double precision' type does not guarantee a 64-bit floating point number
as one might expect. It is instead required to be twice the size of a default
'real', which may vary depending on your system and can be modified by compiler
arguments. For portability, it is recommended to use `real(dp)`, with `dp` set
in one of the following ways:
- `use, intrinsic :: iso_fortran_env, only: dp => real64`
- `integer, parameter :: dp = selected_real_kind(15, 307)`
For code that should be compatible with C, you should instead use
`real(c_double)`, which may be found in the intrinsic module `iso_c_binding`.
"
}

fn entrypoints(&self) -> Vec<&str> {
vec!["intrinsic_type"]
}
}
/// Defines rule to ensure real precision is explicit, as this avoids accidental loss of precision.

fn no_real_suffix(node: &Node, src: &str) -> Option<Violation> {
// Given a number literal, match anything with one or more of a decimal place or
Expand Down Expand Up @@ -125,46 +75,6 @@ mod tests {
use crate::violation;
use textwrap::dedent;

#[test]
fn test_double_precision() -> Result<(), String> {
let source = dedent(
"
double precision function double(x)
double precision, intent(in) :: x
double = 2 * x
end function
subroutine triple(x)
double precision, intent(inout) :: x
x = 3 * x
end subroutine
function complex_mul(x, y)
double precision, intent(in) :: x
double complex, intent(in) :: y
double complex :: complex_mul
complex_mul = x * y
end function
",
);
let expected_violations = [
(2, 1, "double precision"),
(3, 3, "double precision"),
(8, 3, "double precision"),
(13, 3, "double precision"),
(14, 3, "double complex"),
(15, 3, "double complex"),
]
.iter()
.map(|(line, col, kind)| {
let msg = double_precision_err_msg(kind).unwrap();
violation!(&msg, *line, *col)
})
.collect();
test_tree_method(&DoublePrecision {}, source, Some(expected_violations))?;
Ok(())
}

#[test]
fn test_no_real_suffix() -> Result<(), String> {
let source = dedent(
Expand Down
2 changes: 2 additions & 0 deletions src/rules/precision/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod double_precision;
pub mod kind_suffixes;
1 change: 0 additions & 1 deletion src/rules/typing/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod implicit_typing;
pub mod literal_kinds;
pub mod real_precision;
pub mod star_kinds;

0 comments on commit 892bb01

Please sign in to comment.