Skip to content

Commit

Permalink
Make integer syntax style check configurable
Browse files Browse the repository at this point in the history
Ref. eng/recordflux/RecordFlux#1775
  • Loading branch information
andrestt committed Nov 6, 2024
1 parent 60dc679 commit c880a8d
Show file tree
Hide file tree
Showing 18 changed files with 584 additions and 155 deletions.
17 changes: 16 additions & 1 deletion doc/user_guide/90-appendix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ Style Checks
^^^^^^^^^^^^

By default, the style of specification files is checked.
Error messages about style violations have a style check identifier appended to the message in the following form "[style:<identifier>]".
For example: "[style:line-length]".
Style checks can be disabled for individual files by adding a pragma to the first line of the file.
Besides the deactivation of specific checks, it is also possible to disable all checks by using ``all``.

**Example**

Expand All @@ -65,6 +66,20 @@ Besides the deactivation of specific checks, it is also possible to disable all
end P;
It is also possible to disable all style checks by using the identifier ``all``.

**Example**

.. doc-check: rflx
.. code:: rflx
-- style: disable = all
package P is
end P;
Integration Files
^^^^^^^^^^^^^^^^^

Expand Down
17 changes: 17 additions & 0 deletions rflx/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import multiprocessing
from enum import Enum
from pathlib import Path
from typing import Final

Expand All @@ -15,6 +16,22 @@
# RecordFlux is executed by another process, e.g., when the language server is started by VS Code.
MP_CONTEXT = multiprocessing.get_context("forkserver")


class StyleCheck(Enum):
ALL = "all"
BLANK_LINES = "blank-lines"
CHARACTERS = "characters"
INDENTATION = "indentation"
INTEGER_SYNTAX = "integer-syntax"
LINE_LENGTH = "line-length"
TOKEN_SPACING = "token-spacing"
TRAILING_SPACES = "trailing-spaces"


MODEL_STYLE_CHECKS: Final = frozenset({StyleCheck.INTEGER_SYNTAX})
BASIC_STYLE_CHECKS: Final = frozenset(set(StyleCheck) - MODEL_STYLE_CHECKS)


RESERVED_WORDS = [
# Ada
"abort",
Expand Down
2 changes: 2 additions & 0 deletions rflx/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@dataclass
class UncheckedModel(Base):
declarations: Sequence[top_level_declaration.UncheckedTopLevelDeclaration]
style_checks: dict[Path, frozenset[const.StyleCheck]]
error: RecordFluxError

def checked(
Expand All @@ -43,6 +44,7 @@ def checked(
else:
logging.info("Verifying {identifier}", identifier=d.identifier)
checked = d.checked(declarations, workers=workers)
checked.check_style(error, self.style_checks)
declarations.append(checked)
cache.add_verified(digest)
except RecordFluxError as e: # noqa: PERF203
Expand Down
9 changes: 9 additions & 0 deletions rflx/model/top_level_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from abc import abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path

from rflx import const
from rflx.common import Base
from rflx.identifier import ID, StrID
from rflx.rapidflux import NO_LOCATION, ErrorEntry, Location, RecordFluxError, Severity
Expand Down Expand Up @@ -32,6 +34,13 @@ def name(self) -> str:
def package(self) -> ID:
return self.identifier.parent

def check_style(
self,
error: RecordFluxError,
style_checks: dict[Path, frozenset[const.StyleCheck]],
) -> None:
pass

def _check_identifier(self) -> None:
if len(self.identifier.parts) != 2:
self.error.extend(
Expand Down
79 changes: 44 additions & 35 deletions rflx/model/type_decl.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,13 @@ def constraints(


class Integer(Scalar):
def __init__( # noqa: PLR0913
def __init__(
self,
identifier: StrID,
first: expr.Expr,
last: expr.Expr,
size: expr.Expr,
location: Location = NO_LOCATION,
allow_full_unsigned: bool = False,
) -> None:
super().__init__(identifier, size, location)

Expand Down Expand Up @@ -194,7 +193,7 @@ def simplify(expression: expr.Expr) -> expr.Expr:
)

# Eng/RecordFlux/RecordFlux#1077
# size of integers is limited to 63bits
# size of integers is limited to 63 bits

if int(size_num) > const.MAX_SCALAR_SIZE:
self.error.push(
Expand All @@ -205,29 +204,6 @@ def simplify(expression: expr.Expr) -> expr.Expr:
),
)

if (
not allow_full_unsigned
and int(first_num) == 0
and int(last_num) == 2 ** int(size_num) - 1
):
assert self.location is not None
self.error.push(
ErrorEntry(
"unsigned integer syntax preferable",
Severity.ERROR,
self.location,
annotations=(
[
Annotation(
f'use "type {self.name} is unsigned {int(size_num)}" instead',
Severity.HELP,
self.location,
),
]
),
),
)

self.error.propagate()

self._first_expr = first
Expand Down Expand Up @@ -272,6 +248,40 @@ def last(self) -> expr.Number:
def last_expr(self) -> expr.Expr:
return self._last_expr

def check_style(
self,
error: RecordFluxError,
style_checks: dict[Path, frozenset[const.StyleCheck]],
) -> None:
if not self.location or not self.location.source:
return

checks = style_checks.get(self.location.source)
if not checks or const.StyleCheck.INTEGER_SYNTAX not in checks:
return

if int(self.first) == 0 and int(self.last) == 2 ** int(self.size) - 1:
self.error.push(
ErrorEntry(
f'"{self.name}" covers the entire range of an unsigned integer type'
f" [style:{const.StyleCheck.INTEGER_SYNTAX.value}]",
Severity.ERROR,
self.location,
annotations=(
[
Annotation(
f'use "type {self.name} is unsigned {int(self.size)}" instead',
Severity.HELP,
self.location,
),
]
),
generate_default_annotation=False,
),
)

error.extend(self.error.entries)

def constraints(
self,
name: str,
Expand Down Expand Up @@ -343,12 +353,18 @@ def __init__(
),
size,
location=location,
allow_full_unsigned=True,
)

def __str__(self) -> str:
return f"type {self.name} is unsigned {self.size_expr}"

def check_style(
self,
error: RecordFluxError,
style_checks: dict[Path, frozenset[const.StyleCheck]],
) -> None:
pass


class Enumeration(Scalar):
def __init__( # noqa: PLR0912
Expand Down Expand Up @@ -720,14 +736,7 @@ def checked(
skip_verification: bool = False, # noqa: ARG002
workers: int = 1, # noqa: ARG002
) -> Integer:
return Integer(
self.identifier,
self.first,
self.last,
self.size,
location=self.location,
allow_full_unsigned=False,
)
return Integer(self.identifier, self.first, self.last, self.size, self.location)


@dataclass
Expand Down
30 changes: 23 additions & 7 deletions rflx/specification/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dataclasses import dataclass
from pathlib import Path

from rflx import expr, lang, model, ty
from rflx import const, expr, lang, model, ty
from rflx.common import STDIN, unique
from rflx.const import RESERVED_WORDS
from rflx.error import fail
Expand Down Expand Up @@ -65,7 +65,7 @@ def diagnostics_to_error(
error: RecordFluxError,
filename: Path,
) -> bool:
"""Append langkit diagnostics to RecordFlux error. Return True if error occured."""
"""Append langkit diagnostics to RecordFlux error. Return True if error occurred."""

if len(diagnostics) == 0:
return False
Expand Down Expand Up @@ -1758,12 +1758,14 @@ class SpecificationFile:
filename: Path
spec: lang.Specification
context_clauses: list[ContextClause]
model_style_checks: frozenset[const.StyleCheck]

@staticmethod
def create(
error: RecordFluxError,
spec: lang.Specification,
filename: Path,
model_style_checks: frozenset[const.StyleCheck],
) -> SpecificationFile:
check_naming(error, spec.f_package_declaration, filename)

Expand All @@ -1777,6 +1779,7 @@ def create(
)
for context in spec.f_context_clause
],
model_style_checks,
)

@property
Expand Down Expand Up @@ -1842,7 +1845,13 @@ def parse_string(

if not diagnostics_to_error(unit.diagnostics, error, filename):
assert isinstance(unit.root, lang.Specification)
spec = SpecificationFile.create(error, unit.root, filename)

basic_style_checks, model_style_checks = style.determine_enabled_checks(
error,
string,
filename,
)
spec = SpecificationFile.create(error, unit.root, filename, model_style_checks)
_check_for_duplicate_specifications(error, [*self._specifications.values(), spec])

self._specifications[spec.package] = spec
Expand All @@ -1851,7 +1860,7 @@ def parse_string(

self._specifications = _sort_specs_topologically(self._specifications)

error.extend(style.check_string(string, filename).entries)
style.check_string(error, string, basic_style_checks, filename)

error.propagate()

Expand All @@ -1862,10 +1871,12 @@ def create_unchecked_model(self) -> model.UncheckedModel:
model.UNCHECKED_OPAQUE,
]

style_checks: dict[Path, frozenset[const.StyleCheck]] = {}
for spec_node in self._specifications.values():
self._evaluate_specification(error, declarations, spec_node.spec, spec_node.filename)
style_checks[spec_node.filename] = spec_node.model_style_checks

return model.UncheckedModel(declarations, error)
return model.UncheckedModel(declarations, style_checks, error)

def create_model(self) -> model.Model:
unchecked_model = self.create_unchecked_model()
Expand Down Expand Up @@ -1895,7 +1906,12 @@ def _parse_file(self, error: RecordFluxError, filename: Path) -> SpecificationFi
source_code.register(filename, source_code_str)
unit = lang.AnalysisContext().get_from_buffer(str(filename), source_code_str)

error.extend(style.check(filename).entries)
basic_style_checks, model_style_checks = style.determine_enabled_checks(
error,
source_code_str,
filename,
)
error.extend(style.check(filename, basic_style_checks).entries)

if diagnostics_to_error(unit.diagnostics, error, filename):
return None
Expand All @@ -1904,7 +1920,7 @@ def _parse_file(self, error: RecordFluxError, filename: Path) -> SpecificationFi

self._integration.load_integration_file(filename, error)

return SpecificationFile.create(error, unit.root, filename)
return SpecificationFile.create(error, unit.root, filename, model_style_checks)

def _parse_withed_files(
self,
Expand Down
Loading

0 comments on commit c880a8d

Please sign in to comment.