use crate::ast::{dtype_is_plain_number, FortitudeNode};
use crate::settings::Settings;
use crate::{ASTRule, Rule, Violation};
use tree_sitter::Node;
pub struct LiteralKind {}
impl Rule for LiteralKind {
fn new(_settings: &Settings) -> Self {
LiteralKind {}
}
fn explain(&self) -> &'static str {
"
Rather than setting an intrinsic type's kind using an integer literal, such as
`real(8)` or `integer(kind=4)`, consider setting kinds using parameters in the
intrinsic module `iso_fortran_env` such as `real64` and `int32`. For
C-compatible types, consider instead `iso_c_binding` types such as
`real(c_double)`.
Although it is widely believed that `real(8)` represents an 8-byte floating
point (and indeed, this is the case for most compilers and architectures),
there is nothing in the standard to mandate this, and compiler vendors are free
to choose any mapping between kind numbers and machine precision. This may lead
to surprising results if your code is ported to another machine or compiler.
For floating point variables, we recommended using `real(sp)` (single
precision), `real(dp)` (double precision), and `real(qp)` (quadruple precision),
using:
```
use, intrinsic :: iso_fortran_env, only: sp => real32, &
dp => real64, &
qp => real128
```
Or alternatively:
```
integer, parameter :: sp = selected_real_kind(6, 37)
integer, parameter :: dp = selected_real_kind(15, 307)
integer, parameter :: qp = selected_real_kind(33, 4931)
```
Some prefer to set one precision parameter `wp` (working precision), which is
set in one module and used throughout a project.
Integer sizes may be set similarly:
```
integer, parameter :: i1 = selected_int_kind(2) ! 8 bits
integer, parameter :: i2 = selected_int_kind(4) ! 16 bits
integer, parameter :: i4 = selected_int_kind(9) ! 32 bits
integer, parameter :: i8 = selected_int_kind(18) ! 64 bits
```
Or:
```
use, intrinsic :: iso_fortran_env, only: i1 => int8, &
i2 => int16, &
i4 => int32, &
i8 => int64
```
"
}
}
impl ASTRule for LiteralKind {
fn check(&self, node: &Node, src: &str) -> Option<Vec<Violation>> {
let dtype = node.child(0)?.to_text(src)?.to_lowercase();
if !dtype_is_plain_number(dtype.as_str()) {
return None;
}
let kind_node = node.child_by_field_name("kind")?;
let literal_value = integer_literal_kind(&kind_node, src)?;
let msg = format!(
"{dtype} kind set with number literal '{}', use 'iso_fortran_env' parameter",
literal_value.to_text(src)?
);
some_vec![Violation::from_node(msg, &literal_value)]
}
fn entrypoints(&self) -> Vec<&'static str> {
vec!["intrinsic_type"]
}
}
fn integer_literal_kind<'a>(node: &'a Node, src: &str) -> Option<Node<'a>> {
if let Some(literal) = node.child_with_name("number_literal") {
return Some(literal);
}
for child in node.named_children(&mut node.walk()) {
if child.kind() == "number_literal" {
return Some(child);
}
if child.kind() != "keyword_argument" {
continue;
}
let name = child.child_by_field_name("name")?;
if &name.to_text(src)?.to_lowercase() != "kind" {
continue;
}
let value = child.child_by_field_name("value")?;
if value.kind() == "number_literal" {
return Some(value);
}
}
None
}
pub struct LiteralKindSuffix {}
impl Rule for LiteralKindSuffix {
fn new(_settings: &Settings) -> Self {
LiteralKindSuffix {}
}
fn explain(&self) -> &'static str {
"
Using an integer literal as a kind specifier gives no guarantees regarding the
precision of the type, as kind numbers are not specified in the Fortran
standards. It is recommended to use parameter types from `iso_fortran_env`:
```
use, intrinsic :: iso_fortran_env, only: sp => real32, dp => real64
```
or alternatively:
```
integer, parameter :: sp => selected_real_kind(6, 37)
integer, parameter :: dp => selected_real_kind(15, 307)
```
Floating point constants can then be specified as follows:
```
real(sp), parameter :: sqrt2 = 1.41421_sp
real(dp), parameter :: pi = 3.14159265358979_dp
```
"
}
}
impl ASTRule for LiteralKindSuffix {
fn check(&self, node: &Node, src: &str) -> Option<Vec<Violation>> {
let kind = node.child_by_field_name("kind")?;
if kind.kind() != "number_literal" {
return None;
}
let msg = format!(
"'{}' has literal suffix '{}', use 'iso_fortran_env' parameter",
node.to_text(src)?,
&kind.to_text(src)?,
);
some_vec![Violation::from_node(msg, &kind)]
}
fn entrypoints(&self) -> Vec<&'static str> {
vec!["number_literal"]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::settings::default_settings;
use crate::violation;
use pretty_assertions::assert_eq;
use textwrap::dedent;
#[test]
fn test_literal_kind() -> anyhow::Result<()> {
let source = dedent(
"
integer(8) function add_if(x, y, z)
integer :: w
integer(kind=2), intent(in) :: x
integer(i32), intent(in) :: y
logical(kind=4), intent(in) :: z
if (x) then
add_if = x + y
else
add_if = x
end if
end function
subroutine complex_mul(x, y)
real(8), intent(in) :: x
complex(4), intent(inout) :: y
real :: z = 0.5
y = y * x
end subroutine
complex(real64) function complex_add(x, y)
real(real64), intent(in) :: x
complex(kind=4), intent(in) :: y
complex_add = y + x
end function
",
);
let expected: Vec<Violation> = [
(2, 9, "integer", 8),
(4, 16, "integer", 2),
(6, 16, "logical", 4),
(16, 8, "real", 8),
(17, 11, "complex", 4),
(24, 16, "complex", 4),
]
.iter()
.map(|(line, col, kind, literal)| {
let msg = format!(
"{kind} kind set with number literal '{literal}', use 'iso_fortran_env' parameter",
);
violation!(&msg, *line, *col)
})
.collect();
let rule = LiteralKind::new(&default_settings());
let actual = rule.apply(source.as_str())?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_literal_kind_suffix() -> anyhow::Result<()> {
let source = dedent(
"
use, intrinsic :: iso_fortran_env, only: sp => real32, dp => real64
real(sp), parameter :: x1 = 1.234567_4
real(dp), parameter :: x2 = 1.234567_dp
real(dp), parameter :: x3 = 1.789d3
real(dp), parameter :: x4 = 9.876_8
real(sp), parameter :: x5 = 2.468_sp
",
);
let expected: Vec<Violation> = [(4, 38, "1.234567_4", "4"), (7, 35, "9.876_8", "8")]
.iter()
.map(|(line, col, num, kind)| {
let msg = format!(
"'{num}' has literal suffix '{kind}', use 'iso_fortran_env' parameter",
);
violation!(&msg, *line, *col)
})
.collect();
let rule = LiteralKindSuffix::new(&default_settings());
let actual = rule.apply(source.as_str())?;
assert_eq!(actual, expected);
Ok(())
}
}