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 2 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
76 changes: 73 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,85 @@
_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_subroutine(cls, subroutine, rule_report, config, sourcefile=None):
"""
...
"""
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:
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)
content_new = re.sub(rf'{re.escape(node.source.string)}',
fixed_node_str, 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}.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/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