Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - linter: missing kind specifier for real literals and possible fix via git patch #334

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 223 additions & 3 deletions lint_rules/lint_rules/ifs_arpege_coding_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from collections import defaultdict
import re
import difflib

try:
from fparser.two.Fortran2003 import Intrinsic_Name
Expand All @@ -22,16 +23,235 @@
_intrinsic_fortran_names = ()

from loki import (
FindInlineCalls, FindNodes, GenericRule, Module, RuleType
FindInlineCalls, FindNodes, GenericRule, Module, RuleType,
ExpressionFinder, ExpressionRetriever, FloatLiteral,
SubstituteExpressions
)
from loki import ir

from loki import ir, fgen
from loki.frontend.util import read_file

__all__ = [
'MissingImplicitNoneRule', 'OnlyParameterGlobalVarRule', 'MissingIntfbRule',
'MissingKindSpecifierRealLiterals'
]


class FindFloatLiterals(ExpressionFinder):
"""
A visitor to collects :any:`FloatLiteral` used in an IR tree.

See :class:`ExpressionFinder`
"""
retriever = ExpressionRetriever(lambda e: isinstance(e, (FloatLiteral,)))

class MissingKindSpecifierRealLiterals(GenericRule):
"""
...
"""

type = RuleType.SERIOUS
fixable = True

docs = {
'id': 'L0',
'title': (
'Real Literals must have a kind specifier. '
),
}


@classmethod
def check_subroutine(cls, subroutine, rule_report, config, **kwargs):
"""
...
"""
literal_nodes = FindFloatLiterals(with_ir_node=True).visit(subroutine.body)
for node, literals in literal_nodes:
for literal in literals:
if literal.kind is None:
rule_report.add(f'Real/Float literal without kind specifier "{literal}"', node)

@classmethod
def fix_subroutinei_test_2(cls, subroutine, rule_report, config, sourcefile=None):
"""
...
"""
for node, literals in literal_nodes:
literal_map = {}
for literal in literals:
if literal.kind is None and 'e' not in literal.value.lower() and 'd' not in literal.value.lower():
literal_map[literal] = FloatLiteral(value=literal.value, kind='JPRB')
if literal_map:
# fixed_node = SubstituteExpressions(literal_map).visit(node)
# for key in literal_map:
# fixed_node = re.sub(rf'{re.escape()}',
# , content, flags = re.S)
indent = int((len(node.source.string) - len(node.source.string.lstrip(' ')))/2)
fixed_node_str = fgen(fixed_node, depth=indent)
with open (f'loki_lint_{subroutine.name}_new_file_fixed_node_str.F90', 'w') as f:
f.write(fixed_node_str)
content_new = re.sub(rf'{re.escape(node.source.string)}',
fixed_node_str, content, flags = re.S)
content = content_new
with open (f'loki_lint_{subroutine.name}_new_file.F90', 'w') as f:
f.write(content_new)
diff = difflib.unified_diff(original_content.splitlines(), content_new.splitlines(),
f'a/{sourcefile.path}', f'b/{sourcefile.path}', lineterm='')
diff_str = '\n'.join(list(diff))
# print(f"---{sourcefile.path}------")
# print(diff_str)
# print(f"--------------------------")
with open (f'loki_lint_{subroutine.name}.approach_2.patch', 'w') as f:
f.write(diff_str)
f.write('\n')

@classmethod
def fix_subroutine_test(cls, subroutine, rule_report, config, sourcefile=None):
"""
...
"""
# sourcefile = subroutine.source.file
print(f"fix_subroutine: subroutine: {subroutine} | subroutine.source: {subroutine.source} | subroutine.source.file: {subroutine.source.file}")
original_content = read_file(str(sourcefile.path))
content = original_content
literals = FindFloatLiterals(with_ir_node=False).visit(subroutine.body)
literal_map = {}
for literal in literals:
if literal.kind is None:
literal_map[literal] = FloatLiteral(value=literal.value, kind='JPRB')
# content_new = content
if literal_map:
for key in literal_map:
# print(f"replace ")
# content_new = re.sub(rf'{re.escape(str(key))}', rf'{re.escape(str(literal_map[key]))}', content, flags = re.S)
content_new = re.sub(rf'{re.escape(str(key))}', str(literal_map[key]), content, flags = re.S)
content = content_new
diff = difflib.unified_diff(original_content.splitlines(), content_new.splitlines(),
f'a/{sourcefile.path}', f'b/{sourcefile.path}', lineterm='')
diff_str = '\n'.join(list(diff))
# print(f"---{sourcefile.path}------")
# print(diff_str)
# print(f"--------------------------")
with open (f'loki_lint_{subroutine.name}.approach_2.patch', 'w') as f:
f.write(diff_str)
f.write('\n')

"""
for node, literals in literal_nodes:
literal_map = {}
for literal in literals:
if literal.kind is None:
literal_map[literal] = FloatLiteral(value=literal.value, kind='JPRB')
if literal_map:
# fixed_node = SubstituteExpressions(literal_map).visit(node)
# indent = int((len(node.source.string) - len(node.source.string.lstrip(' ')))/2)
# fixed_node_str = fgen(fixed_node, depth=indent)
# with open (f'loki_lint_{subroutine.name}_new_file_fixed_node_str.F90', 'w') as f:
#  f.write(fixed_node_str)
# content_new = re.sub(rf'{re.escape(node.source.string)}',
# fixed_node_str, content, flags = re.S)
# content = content_new
with open (f'loki_lint_{subroutine.name}_new_file.F90', 'w') as f:
f.write(content_new)
diff = difflib.unified_diff(original_content.splitlines(), content_new.splitlines(),
f'a/{sourcefile.path}', f'b/{sourcefile.path}', lineterm='')
diff_str = '\n'.join(list(diff))
# print(f"---{sourcefile.path}------")
# print(diff_str)
# print(f"--------------------------")
with open (f'loki_lint_{subroutine.name}.approach_2.patch', 'w') as f:
f.write(diff_str)
f.write('\n')
"""

@classmethod
def fix_subroutine(cls, subroutine, rule_report, config, sourcefile=None):
"""
...
"""
# sourcefile = subroutine.source.file
print(f"fix_subroutine: subroutine: {subroutine} | subroutine.source: {subroutine.source} | subroutine.source.file: {subroutine.source.file}")
original_content = read_file(str(sourcefile.path))
content = original_content
literal_nodes = FindFloatLiterals(with_ir_node=True).visit(subroutine.body)
content_new = None
for node, literals in literal_nodes:
# print(f"node.source: {node.source.string} | {type(node.source.string)}")
literal_map = {}
for literal in literals:
if literal.kind is None and 'e' not in str(literal.value).lower() and 'd' not in str(literal.value).lower():
literal_map[literal] = FloatLiteral(value=literal.value, kind='JPRB')
if literal_map:
# fixed_node = SubstituteExpressions(literal_map).visit(node)
fixed_node = node.source.string
# if hasattr(node, 'comment') and node.comment is not None:
# comment = node.comment
# fixed_node._update(comment=None)
# else:
# comment = None
for key in literal_map:
fixed_node = re.sub(rf'{re.escape(str(key))}',
str(literal_map[key]), fixed_node, flags = re.S)
# indent = int((len(node.source.string) - len(node.source.string.lstrip(' ')))/2)
# fixed_node_str = fgen(fixed_node, depth=indent)
# if comment is not None:
# fixed_node._update(comment=comment)
fixed_node_str = str(fixed_node)
# with open (f'loki_lint_{subroutine.name}_new_file_fixed_node_str.F90', 'w') as f:
# f.write(fixed_node_str)
content_new = re.sub(rf'{re.escape(node.source.string)}',
fixed_node_str, content, flags = re.S)
content = content_new
# with open (f'loki_lint_{subroutine.name}_new_file.F90', 'w') as f:
# f.write(content_new)
if content_new is not None:
diff = difflib.unified_diff(original_content.splitlines(), content_new.splitlines(),
f'a/{sourcefile.path}', f'b/{sourcefile.path}', lineterm='')
diff_str = '\n'.join(list(diff))
# print(f"---{sourcefile.path}------")
# print(diff_str)
# print(f"--------------------------")
with open (f'loki_lint_{subroutine.name}.patch', 'w') as f:
f.write(diff_str)
f.write('\n')

@classmethod
def fix_subroutine_working(cls, subroutine, rule_report, config, sourcefile=None):
"""
...
"""
# sourcefile = subroutine.source.file
print(f"fix_subroutine: subroutine: {subroutine} | subroutine.source: {subroutine.source} | subroutine.source.file: {subroutine.source.file}")
original_content = read_file(str(sourcefile.path))
content = original_content
literal_nodes = FindFloatLiterals(with_ir_node=True).visit(subroutine.body)
for node, literals in literal_nodes:
# print(f"node.source: {node.source.string} | {type(node.source.string)}")
literal_map = {}
for literal in literals:
if literal.kind is None:
literal_map[literal] = FloatLiteral(value=literal.value, kind='JPRB')
if literal_map:
fixed_node = SubstituteExpressions(literal_map).visit(node)
indent = int((len(node.source.string) - len(node.source.string.lstrip(' ')))/2)
fixed_node_str = fgen(fixed_node, depth=indent)
with open (f'loki_lint_{subroutine.name}_new_file_fixed_node_str.F90', 'w') as f:
f.write(fixed_node_str)
content_new = re.sub(rf'{re.escape(node.source.string)}',
fixed_node_str, content, flags = re.S)
content = content_new
with open (f'loki_lint_{subroutine.name}_new_file.F90', 'w') as f:
f.write(content_new)
diff = difflib.unified_diff(original_content.splitlines(), content_new.splitlines(),
f'a/{sourcefile.path}', f'b/{sourcefile.path}', lineterm='')
diff_str = '\n'.join(list(diff))
# print(f"---{sourcefile.path}------")
# print(diff_str)
# print(f"--------------------------")
with open (f'loki_lint_{subroutine.name}.patch', 'w') as f:
f.write(diff_str)
f.write('\n')

class MissingImplicitNoneRule(GenericRule):
"""
``IMPLICIT NONE`` must be present in all scoping units but may be omitted
Expand Down
4 changes: 2 additions & 2 deletions loki/expression/mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def map_quotient(self, expr, enclosing_prec, *args, **kwargs):
numerator = self.rec_with_force_parens_around(expr.numerator, PREC_PRODUCT, *args, **kwargs)
kwargs['force_parens_around'] = self.multiplicative_primitives
denominator = self.rec_with_force_parens_around(expr.denominator, PREC_PRODUCT, *args, **kwargs)
return self.parenthesize_if_needed(self.format('%s / %s', numerator, denominator),
return self.parenthesize_if_needed(self.format('%s/%s', numerator, denominator),
enclosing_prec, PREC_PRODUCT)

def map_parenthesised_add(self, expr, enclosing_prec, *args, **kwargs):
Expand Down Expand Up @@ -206,7 +206,7 @@ def map_inline_do(self, expr, enclosing_prec, *args, **kwargs):

def map_array_subscript(self, expr, enclosing_prec, *args, **kwargs):
name_str = self.rec(expr.aggregate, PREC_NONE, *args, **kwargs)
index_str = self.join_rec(', ', expr.index_tuple, PREC_NONE, *args, **kwargs)
index_str = self.join_rec(',', expr.index_tuple, PREC_NONE, *args, **kwargs)
return f'{name_str}({index_str})'

map_string_subscript = map_array_subscript
Expand Down
18 changes: 17 additions & 1 deletion loki/frontend/tests/test_frontends.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from loki import (
Module, Subroutine, FindVariables, BasicType, config, Sourcefile,
RawSource, RegexParserClass, ProcedureType, DerivedType,
PreprocessorDirective, config_override
PreprocessorDirective, config_override, FindLiterals
)
from loki.build import jit_compile, clean_test
from loki.expression import symbols as sym
Expand Down Expand Up @@ -53,7 +53,23 @@ def fixture_reset_regex_frontend_timeout():
yield
config['regex-frontend-timeout'] = original_timeout

@pytest.mark.parametrize('frontend', available_frontends())
def test_literals_kind(frontend):
"""
"""

fcode = """
SUBROUTINE SOME_SUBROUTINE()
REAL :: A
A = 0.d0
END SUBROUTINE
""".strip()
routine = Subroutine.from_source(fcode, frontend=frontend)
literals = FindLiterals().visit(routine.body)
# print(f"literals: {literals}")
for literal in literals:
print(f"literal: '{literal}' | {literal.kind}")

@pytest.mark.parametrize('frontend', available_frontends())
def test_check_alloc_opts(here, frontend):
"""
Expand Down
3 changes: 2 additions & 1 deletion loki/ir/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def format_line(self, *items, comment=None, no_wrap=False, no_indent=False):
required to observe the line width limit.
:rtype: str
"""
if not no_indent:
# print(f"items: {items}")
if not no_indent and items != ('',):
items = [self.indent, *items]
if no_wrap:
# Simply concatenate items and append the comment
Expand Down
4 changes: 2 additions & 2 deletions loki/lint/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,15 @@ def check(cls, ast, rule_report, config, **kwargs):
cls.check(member, rule_report, config, **kwargs)

@classmethod
def fix_module(cls, module, rule_report, config):
def fix_module(cls, module, rule_report, config, sourcefile=None):
"""
Fix rule violations on module level

Must be implemented by a rule if applicable.
"""

@classmethod
def fix_subroutine(cls, subroutine, rule_report, config):
def fix_subroutine(cls, subroutine, rule_report, config, sourcefile=None):
"""
Fix rule violations on subroutine level

Expand Down
18 changes: 9 additions & 9 deletions loki/lint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Fixer:
"""

@classmethod
def fix_module(cls, module, reports, config): # pylint: disable=unused-argument
def fix_module(cls, module, reports, config, sourcefile): # pylint: disable=unused-argument
"""
Call `fix_module` for all rules and apply the transformations.
"""
Expand All @@ -33,14 +33,14 @@ def fix_module(cls, module, reports, config): # pylint: disable=unused-argument
return module

@classmethod
def fix_subroutine(cls, subroutine, reports, config):
def fix_subroutine(cls, subroutine, reports, config, sourcefile):
"""
Call `fix_subroutine` for all rules and apply the transformations.
"""
mapper = {}
for report in reports:
rule_config = config[report.rule.__name__]
mapper.update(report.rule.fix_subroutine(subroutine, report, rule_config) or {})
mapper.update(report.rule.fix_subroutine(subroutine, report, rule_config, sourcefile) or {})

if mapper:
# Apply the changes and invalidate source objects
Expand Down Expand Up @@ -87,10 +87,10 @@ def fix(cls, ast, reports, config):
# Depth-first traversal
if hasattr(ast, 'subroutines') and ast.subroutines is not None:
for routine in ast.subroutines:
cls.fix_subroutine(routine, reports, config)
cls.fix_subroutine(routine, reports, config, ast)
if hasattr(ast, 'modules') and ast.modules is not None:
for module in ast.modules:
cls.fix_module(module, reports, config)
cls.fix_module(module, reports, config, ast)

cls.fix_sourcefile(ast, reports, config)

Expand All @@ -99,18 +99,18 @@ def fix(cls, ast, reports, config):
# Depth-first traversal
if hasattr(ast, 'subroutines') and ast.subroutines is not None:
for routine in ast.subroutines:
cls.fix_subroutine(routine, reports, config)
cls.fix_subroutine(routine, reports, config, ast)

cls.fix_module(ast, reports, config)
cls.fix_module(ast, reports, config, ast)

# Fix on subroutine level
elif isinstance(ast, Subroutine):
# Depth-first traversal
if hasattr(ast, 'members') and ast.members is not None:
for routine in ast.members:
cls.fix_subroutine(routine, reports, config)
cls.fix_subroutine(routine, reports, config, ast)

cls.fix_subroutine(ast, reports, config)
cls.fix_subroutine(ast, reports, config, ast)

return ast

Expand Down
Loading