From 43a816e6cddb5fd0fee85abe576b3a4d22575531 Mon Sep 17 00:00:00 2001 From: Liam Pattinson Date: Sun, 15 Sep 2024 19:40:10 +0100 Subject: [PATCH 1/2] Update lock file --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c4e57b6..e951efc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,7 +139,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "fortitude" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", From 78d064b757d4f4d0769d2823afd236069354d336 Mon Sep 17 00:00:00 2001 From: Liam Pattinson Date: Mon, 16 Sep 2024 09:03:35 +0100 Subject: [PATCH 2/2] Add precision category, reorder typing rules --- src/lib.rs | 4 + src/rules/mod.rs | 45 ++++---- src/rules/precision/double_precision.rs | 102 ++++++++++++++++++ .../kind_suffixes.rs} | 92 +--------------- src/rules/precision/mod.rs | 2 + src/rules/typing/mod.rs | 1 - 6 files changed, 132 insertions(+), 114 deletions(-) create mode 100644 src/rules/precision/double_precision.rs rename src/rules/{typing/real_precision.rs => precision/kind_suffixes.rs} (54%) create mode 100644 src/rules/precision/mod.rs diff --git a/src/lib.rs b/src/lib.rs index bc3bcd7..b36a7c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, } @@ -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) @@ -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) diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 2bf0ceb..801e601 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -1,6 +1,7 @@ mod error; mod filesystem; mod modules; +mod precision; mod style; mod typing; use crate::{Category, Code, Rule}; @@ -37,39 +38,39 @@ pub fn build_rule(code_str: &str) -> anyhow::Result { 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, @@ -87,8 +88,8 @@ pub fn build_rule(code_str: &str) -> anyhow::Result { // 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())) } diff --git a/src/rules/precision/double_precision.rs b/src/rules/precision/double_precision.rs new file mode 100644 index 0000000..f8d3030 --- /dev/null +++ b/src/rules/precision/double_precision.rs @@ -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 { + 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 { + 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(()) + } +} diff --git a/src/rules/typing/real_precision.rs b/src/rules/precision/kind_suffixes.rs similarity index 54% rename from src/rules/typing/real_precision.rs rename to src/rules/precision/kind_suffixes.rs index 2e1d7db..19a14ba 100644 --- a/src/rules/typing/real_precision.rs +++ b/src/rules/precision/kind_suffixes.rs @@ -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 { - 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 { - 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 { // Given a number literal, match anything with one or more of a decimal place or @@ -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( diff --git a/src/rules/precision/mod.rs b/src/rules/precision/mod.rs new file mode 100644 index 0000000..1088c02 --- /dev/null +++ b/src/rules/precision/mod.rs @@ -0,0 +1,2 @@ +pub mod double_precision; +pub mod kind_suffixes; diff --git a/src/rules/typing/mod.rs b/src/rules/typing/mod.rs index 6029195..770389f 100644 --- a/src/rules/typing/mod.rs +++ b/src/rules/typing/mod.rs @@ -1,4 +1,3 @@ pub mod implicit_typing; pub mod literal_kinds; -pub mod real_precision; pub mod star_kinds;