From 7bddb42d781ad053bcbcd2ee5a125a0684686d78 Mon Sep 17 00:00:00 2001 From: Corentin Machu Date: Tue, 2 Jul 2024 11:24:50 +0200 Subject: [PATCH] Fix usage of reserved keyword in condition Ref. eng/recordflux/RecordFlux#1715 --- librapidflux/src/identifier.rs | 11 +++ rapidflux/src/identifier.rs | 4 + rflx/const.py | 84 +++++++++++++++++++ rflx/converter/iana.py | 4 +- rflx/ls/server.py | 1 - rflx/model/message.py | 14 ++-- rflx/specification/const.py | 83 ------------------ rflx/specification/parser.py | 12 ++- tests/integration/specification_model_test.py | 56 +++++++++++++ tests/property/strategies.py | 3 +- tests/unit/specification/const_test.py | 2 - tests/unit/specification/parser_test.py | 25 ++++++ 12 files changed, 200 insertions(+), 99 deletions(-) delete mode 100644 rflx/specification/const.py delete mode 100644 tests/unit/specification/const_test.py diff --git a/librapidflux/src/identifier.rs b/librapidflux/src/identifier.rs index f4fe7d94f..e81f0f8fa 100644 --- a/librapidflux/src/identifier.rs +++ b/librapidflux/src/identifier.rs @@ -163,6 +163,12 @@ impl std::cmp::PartialEq for ID { } } +impl AsRef for ID { + fn as_ref(&self) -> &str { + &self.identifier + } +} + #[derive(Debug, PartialEq)] pub struct IDRef<'a> { identifier: &'a str, @@ -378,6 +384,11 @@ mod tests { assert_eq!(id(left, None) == id(right, None), expected); } + #[test] + fn test_id_as_ref() { + assert_eq!(id("foo", None).as_ref(), "foo"); + } + #[test] fn test_id_ref_to_owned() { let identifier = "A::B".to_string(); diff --git a/rapidflux/src/identifier.rs b/rapidflux/src/identifier.rs index 52e1a3c6e..6dc8c086f 100644 --- a/rapidflux/src/identifier.rs +++ b/rapidflux/src/identifier.rs @@ -63,6 +63,10 @@ impl ID { PyBool::new_bound(py, self.0 == other_id.0) .to_owned() .into() + } else if let Ok(other_str) = other.extract::<&str>() { + PyBool::new_bound(py, self.0.as_ref() == other_str) + .to_owned() + .into() } else { PyNotImplemented::get_bound(py).to_owned().into() } diff --git a/rflx/const.py b/rflx/const.py index bf7c65045..d8c704986 100644 --- a/rflx/const.py +++ b/rflx/const.py @@ -15,3 +15,87 @@ # The use of "forkserver" or "spawn" as start method for starting processes prevents deadlocks when # RecordFlux is executed by another process, e.g., when the language server is started by VS Code. MP_CONTEXT = multiprocessing.get_context("forkserver") + +RESERVED_WORDS = [ + # Ada + "abort", + "abs", + "abstract", + "accept", + "access", + "aliased", + "all", + "and", + "array", + "at", + "begin", + "body", + "case", + "constant", + "declare", + "delay", + "delta", + "digits", + "do", + "else", + "elsif", + "end", + "entry", + "exception", + "exit", + "for", + "function", + "generic", + "goto", + "if", + "in", + "interface", + "is", + "limited", + "loop", + "mod", + "new", + "not", + "null", + "of", + "or", + "others", + "out", + "overriding", + "package", + "parallel", + "pragma", + "private", + "procedure", + "protected", + "raise", + "range", + "record", + "rem", + "renames", + "requeue", + "return", + "reverse", + "select", + "separate", + "some", + "subtype", + "synchronized", + "tagged", + "task", + "terminate", + "then", + "type", + "until", + "use", + "when", + "while", + "with", + "xor", + # Code generation + "initial", + "final", + "ctx", + "val", + "enum", +] diff --git a/rflx/converter/iana.py b/rflx/converter/iana.py index 24e2b4cdb..9fec94211 100644 --- a/rflx/converter/iana.py +++ b/rflx/converter/iana.py @@ -12,13 +12,13 @@ from defusedxml import ElementTree -import rflx.specification.const +import rflx.const from rflx.common import file_name from rflx.error import fail, warn from rflx.rapidflux import Location, RecordFluxError NAMESPACE = {"iana": "http://www.iana.org/assignments"} -RESERVED_WORDS = "|".join(rflx.specification.const.RESERVED_WORDS) +RESERVED_WORDS = "|".join(rflx.const.RESERVED_WORDS) OUTPUT_INDENT_CHAR = " " OUTPUT_INDENT = OUTPUT_INDENT_CHAR * 3 OUTPUT_WIDTH = 80 diff --git a/rflx/ls/server.py b/rflx/ls/server.py index 55f741408..495abf6be 100644 --- a/rflx/ls/server.py +++ b/rflx/ls/server.py @@ -158,7 +158,6 @@ def update(self, document_uri: str) -> None: self._error.extend(e.entries) unchecked_model = parser.create_unchecked_model() - self._error.extend(unchecked_model.error.entries) self._publish_errors_as_diagnostics(self._error) diff --git a/rflx/model/message.py b/rflx/model/message.py index f36942d27..eb719fe7d 100644 --- a/rflx/model/message.py +++ b/rflx/model/message.py @@ -1533,14 +1533,12 @@ def _verify_message_types(self) -> None: continue for var in expression.variables(): if var.type_ == rty.Undefined(): - self.error.extend( - [ - ErrorEntry( - f'undefined variable "{var.identifier}"', - Severity.ERROR, - var.location, - ), - ], + self.error.push( + ErrorEntry( + f'undefined variable "{var.identifier}"', + Severity.ERROR, + var.location, + ), ) def _verify_expression_types(self, valid_paths: set[tuple[Link, ...]]) -> None: diff --git a/rflx/specification/const.py b/rflx/specification/const.py deleted file mode 100644 index 989f98cf2..000000000 --- a/rflx/specification/const.py +++ /dev/null @@ -1,83 +0,0 @@ -RESERVED_WORDS = [ - # Ada - "abort", - "abs", - "abstract", - "accept", - "access", - "aliased", - "all", - "and", - "array", - "at", - "begin", - "body", - "case", - "constant", - "declare", - "delay", - "delta", - "digits", - "do", - "else", - "elsif", - "end", - "entry", - "exception", - "exit", - "for", - "function", - "generic", - "goto", - "if", - "in", - "interface", - "is", - "limited", - "loop", - "mod", - "new", - "not", - "null", - "of", - "or", - "others", - "out", - "overriding", - "package", - "parallel", - "pragma", - "private", - "procedure", - "protected", - "raise", - "range", - "record", - "rem", - "renames", - "requeue", - "return", - "reverse", - "select", - "separate", - "some", - "subtype", - "synchronized", - "tagged", - "task", - "terminate", - "then", - "type", - "until", - "use", - "when", - "while", - "with", - "xor", - # Code generation - "initial", - "final", - "ctx", - "val", - "enum", -] diff --git a/rflx/specification/parser.py b/rflx/specification/parser.py index 6b6337423..1dc6a1c20 100644 --- a/rflx/specification/parser.py +++ b/rflx/specification/parser.py @@ -10,6 +10,7 @@ import rflx.typing_ as rty from rflx import expr, lang, model from rflx.common import STDIN, unique +from rflx.const import RESERVED_WORDS from rflx.error import fail from rflx.identifier import ID, StrID from rflx.integration import Integration @@ -23,7 +24,6 @@ logging, source_code, ) -from rflx.specification.const import RESERVED_WORDS from . import style @@ -336,6 +336,16 @@ def create_id(error: RecordFluxError, identifier: lang.AbstractID, filename: Pat ) * name ) + + if name.parts[0].lower() in RESERVED_WORDS: + error.push( + ErrorEntry( + f'reserved word "{name}" used as identifier', + Severity.ERROR, + node_location(identifier, filename), + ), + ) + return name raise NotImplementedError(f"Invalid ID: {identifier.text} {type(identifier)}") diff --git a/tests/integration/specification_model_test.py b/tests/integration/specification_model_test.py index c3b48c2fb..a452f282a 100644 --- a/tests/integration/specification_model_test.py +++ b/tests/integration/specification_model_test.py @@ -7,6 +7,7 @@ import pytest from rflx import expr, typing_ as rty +from rflx.const import RESERVED_WORDS from rflx.identifier import ID from rflx.model import ( BOOLEAN, @@ -2151,3 +2152,58 @@ def test_parameter_non_scalar_and_builtin_type( ), capfd, ) + + +@pytest.mark.parametrize( + "keyword", + RESERVED_WORDS, +) +def test_reserved_word_link_condition( + tmp_path: Path, + capfd: pytest.CaptureFixture[str], + keyword: str, +) -> None: + file_path = tmp_path / "test.rflx" + file_path.write_text( + textwrap.dedent( + f"""\ + package Test is + type I is range 0 .. 255 with Size => 8; + type M (A : Boolean) is + message + X : Boolean + then Z + if A and {keyword}; + Z : Opaque + with Size => 8; + end message; + end Test; + """, + ), + ) + assert_error_full_message( + file_path, + textwrap.dedent( + f"""\ + info: Parsing {file_path} + info: Processing Test + info: Verifying __BUILTINS__::Boolean + info: Verifying __INTERNAL__::Opaque + info: Verifying Test::I + info: Verifying Test::M + error: reserved word "{keyword}" used as identifier + --> {file_path}:7:25 + | + 7 | if A and {keyword}; + | {"^".rjust(len(keyword), "^")} + | + error: undefined variable "{keyword}" + --> {file_path}:7:25 + | + 7 | if A and {keyword}; + | {"^".rjust(len(keyword), "^")} + | + """, + ), + capfd, + ) diff --git a/tests/property/strategies.py b/tests/property/strategies.py index 2de2b5493..cce3f75ae 100644 --- a/tests/property/strategies.py +++ b/tests/property/strategies.py @@ -7,7 +7,7 @@ from hypothesis import assume, strategies as st -from rflx import expr, typing_ as rty +from rflx import const, expr, typing_ as rty from rflx.identifier import ID from rflx.model import ( BUILTIN_TYPES, @@ -28,7 +28,6 @@ TypeDecl, ) from rflx.rapidflux import ErrorEntry, Location, RecordFluxError, Severity -from rflx.specification import const T = TypeVar("T") diff --git a/tests/unit/specification/const_test.py b/tests/unit/specification/const_test.py deleted file mode 100644 index ebfa4f3da..000000000 --- a/tests/unit/specification/const_test.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_dummy() -> None: - pass diff --git a/tests/unit/specification/parser_test.py b/tests/unit/specification/parser_test.py index 6e327359d..a28a418f7 100644 --- a/tests/unit/specification/parser_test.py +++ b/tests/unit/specification/parser_test.py @@ -10,6 +10,7 @@ import pytest from rflx import common, expr, lang, model +from rflx.const import RESERVED_WORDS from rflx.error import fail from rflx.identifier import ID from rflx.model import ( @@ -3453,6 +3454,30 @@ def test_parse_reserved_word_as_channel_name() -> None: ) +@pytest.mark.parametrize( + "keyword", + RESERVED_WORDS, +) +def test_parse_reserved_word_in_link_condition(keyword: str) -> None: + assert_error_string( + f"""\ + package Test is + type I is range 0 .. 255 with Size => 8; + type M (A : Boolean) is + message + X : Boolean + then Z + if A and {keyword}; + Z : Opaque + with Size => 8; + end message; + end Test; + """, + rf'^:7:25: error: reserved word "{keyword}" used as identifier\n' + rf':7:25: error: undefined variable "{keyword}"$', + ) + + def test_parse_error_duplicate_message_aspect() -> None: assert_error_string( """\