diff --git a/aas_core_codegen/java/transpilation.py b/aas_core_codegen/java/transpilation.py index bb91c70c7..bb284ac6c 100644 --- a/aas_core_codegen/java/transpilation.py +++ b/aas_core_codegen/java/transpilation.py @@ -4,6 +4,7 @@ import io import textwrap from typing import ( + Final, Tuple, List, Mapping, @@ -158,6 +159,27 @@ def _is_parenthesized_is_instance(node: parse_tree.Node) -> bool: return isinstance(node, parse_tree.IsInstance) and len(node.classes) > 1 +#: Map the primitive types to the primitive Java types which unbox their values, +#: see :py:meth:`Transpiler.transform_comparison` +_UNBOXED_TYPE_BY_PRIMITIVE_TYPE: Final[ + Mapping[intermediate_type_inference.PrimitiveType, str] +] = { + intermediate_type_inference.PrimitiveType.BOOL: "boolean", + intermediate_type_inference.PrimitiveType.INT: "long", + intermediate_type_inference.PrimitiveType.FLOAT: "double", +} + +#: List the nodes which are transpiled to Java primitives, and never to boxed values, +#: see :py:meth:`Transpiler.transform_comparison` +_CERTAINLY_PRIMITIVE_NODE_TYPES: Final = ( + parse_tree.Constant, + parse_tree.Add, + parse_tree.Sub, + parse_tree.Mod, + parse_tree.Neg, +) + + class Transpiler( parse_tree.RestrictedTransformer[Tuple[Optional[Stripped], Optional[Error]]] ): @@ -647,12 +669,46 @@ def transform_comparison( parse_tree.Slice, ) - if isinstance(node.left, no_parentheses_types) and isinstance( - node.right, no_parentheses_types + if not ( + isinstance(node.left, no_parentheses_types) + and isinstance(node.right, no_parentheses_types) ): - return Stripped(f"{left} {comparator} {right}"), None + left = Stripped(f"({left})") + right = Stripped(f"({right})") + + # NOTE (mristin): + # We represent the integers, the floats and the booleans as ``Long``'s, + # ``Double``'s and ``Boolean``'s in the arguments, the properties and + # the collections. The operators ``==`` and ``!=`` compare the references of + # two such boxed values, which are equal only by chance, *e.g.*, for + # the ``Long``'s between -128 and 127, which Java caches. Hence, we unbox + # the left operand, unless an operand is certainly a primitive already, as + # Java then unboxes the other operand itself. + if node.op in (parse_tree.Comparator.EQ, parse_tree.Comparator.NE): + left_primitive_type = intermediate_type_inference.try_primitive_type( + self.type_map[node.left] + ) + right_primitive_type = intermediate_type_inference.try_primitive_type( + self.type_map[node.right] + ) + + unboxed_type = ( + _UNBOXED_TYPE_BY_PRIMITIVE_TYPE.get(left_primitive_type, None) + if left_primitive_type is not None + else None + ) + + if ( + unboxed_type is not None + and right_primitive_type is not None + and right_primitive_type + is not intermediate_type_inference.PrimitiveType.LENGTH + and not isinstance(node.left, _CERTAINLY_PRIMITIVE_NODE_TYPES) + and not isinstance(node.right, _CERTAINLY_PRIMITIVE_NODE_TYPES) + ): + left = Stripped(f"({unboxed_type}) {left}") - return Stripped(f"({left}) {comparator} ({right})"), None + return Stripped(f"{left} {comparator} {right}"), None @ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) def transform_is_in( diff --git a/dev/test_data/common_meta_models/list_of_primitives.py b/dev/test_data/common_meta_models/list_of_primitives.py index 018f12e00..677204b51 100644 --- a/dev/test_data/common_meta_models/list_of_primitives.py +++ b/dev/test_data/common_meta_models/list_of_primitives.py @@ -60,6 +60,39 @@ def ints_are_unique(numbers: Sequence[int]) -> bool: return True +@verification +def contains_by_loop(number: int, numbers: Sequence[int]) -> bool: + """ + Check whether :paramref:`number` is in :paramref:`numbers`, item by item. + + Java boxes both :paramref:`number` and the items as ``Long``'s, so that ``==`` + would compare their references. The test data looks up the numbers beyond -128 + and 127 on purpose, as Java caches the boxed values in that range, and + the references would be equal by chance. + """ + for other in numbers: + if other == number: + return True + + return False + + +@verification +def floats_are_unique(numbers: Sequence[float]) -> bool: + """ + Check that :paramref:`numbers` are unique by comparing them pair-wise. + + Java boxes the items as ``Double``'s, so that ``==`` would compare their + references. + """ + for i in range(0, len(numbers)): + for j in range(i + 1, len(numbers)): + if numbers[i] == numbers[j]: + return False + + return True + + @verification def is_separator(text: str) -> bool: """Check whether :paramref:`text` is one of the separators.""" @@ -87,6 +120,14 @@ def contains_dot(text: str) -> bool: return "." in text +@invariant( + lambda self: floats_are_unique(self.some_floats), + "Some floats must be unique.", +) +@invariant( + lambda self: not contains_by_loop(-500, self.some_ints), + "Some ints must not contain -500.", +) @invariant( lambda self: all(not is_separator(text) for text in self.some_strings), "Some strings must not be separators.", diff --git a/dev/test_data/intermediate/common_meta_models/list_of_primitives/expected_symbol_table.txt b/dev/test_data/intermediate/common_meta_models/list_of_primitives/expected_symbol_table.txt index d98ae77c8..eae620e9d 100644 --- a/dev/test_data/intermediate/common_meta_models/list_of_primitives/expected_symbol_table.txt +++ b/dev/test_data/intermediate/common_meta_models/list_of_primitives/expected_symbol_table.txt @@ -421,6 +421,41 @@ SymbolTable( original_node=...), original_node=...)"""), specified_for='Reference to ConcreteClass Something', + parsed=...), + Invariant( + description='Some ints must not contain -500.', + body=textwrap.dedent("""\ + Not( + operand=FunctionCall( + name='contains_by_loop', + args=[ + Constant( + value=-500, + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_ints', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Something', + parsed=...), + Invariant( + description='Some floats must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='floats_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_floats', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Something', parsed=...)], serialization=Serialization( with_model_type=False), @@ -561,6 +596,80 @@ SymbolTable( postconditions=[]), parsed=..., arguments_by_name=...), + TranspilableVerification( + name='contains_by_loop', + visibility='PUBLIC', + arguments=[ + Argument( + name='number', + type_annotation=PrimitiveTypeAnnotation( + a_type='INT', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='numbers', + type_annotation=ListTypeAnnotation( + items=PrimitiveTypeAnnotation( + a_type='INT', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Check whether number is in numbers, item by item.', + remarks=[ + textwrap.dedent("""\ + Java boxes both number and the items as Long's, so that == + would compare their references. The test data looks up the numbers beyond -128 + and 127 on purpose, as Java caches the boxed values in that range, and + the references would be equal by chance.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='floats_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='numbers', + type_annotation=ListTypeAnnotation( + items=PrimitiveTypeAnnotation( + a_type='FLOAT', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Check that numbers are unique by comparing them pair-wise.', + remarks=[ + textwrap.dedent("""\ + Java boxes the items as Double's, so that == would compare their + references.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), TranspilableVerification( name='is_separator', visibility='PUBLIC', diff --git a/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/floats_not_unique.json b/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/floats_not_unique.json new file mode 100644 index 000000000..9edadca04 --- /dev/null +++ b/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/floats_not_unique.json @@ -0,0 +1,10 @@ +{ + "someBools": [], + "someInts": [], + "someFloats": [ + 2.5, + 2.5 + ], + "someStrings": [], + "someBytes": [] +} diff --git a/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/ints_contain_minus_500.json b/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/ints_contain_minus_500.json new file mode 100644 index 000000000..6647e2299 --- /dev/null +++ b/dev/test_data/live_tests/common_test_data/list_of_primitives/Json/Unexpected/Invalid/InvariantViolation/Something/ints_contain_minus_500.json @@ -0,0 +1,9 @@ +{ + "someBools": [], + "someInts": [ + -500 + ], + "someFloats": [], + "someStrings": [], + "someBytes": [] +} diff --git a/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/floats_not_unique.xml b/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/floats_not_unique.xml new file mode 100644 index 000000000..e528fef0a --- /dev/null +++ b/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/floats_not_unique.xml @@ -0,0 +1 @@ +2.52.5 \ No newline at end of file diff --git a/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/ints_contain_minus_500.xml b/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/ints_contain_minus_500.xml new file mode 100644 index 000000000..af2b580af --- /dev/null +++ b/dev/test_data/live_tests/common_test_data/list_of_primitives/Xml/Unexpected/Invalid/InvariantViolation/something/ints_contain_minus_500.xml @@ -0,0 +1 @@ +-500 \ No newline at end of file diff --git a/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/include/dummy/verification.hpp index d2f0ffa5e..8ff07e305 100644 --- a/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/include/dummy/verification.hpp +++ b/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/include/dummy/verification.hpp @@ -234,6 +234,25 @@ bool IntsAreUnique( const std::vector& numbers ); +/// \brief Check whether \p number is in \p numbers, item by item. +/// +/// Java boxes both \p number and the items as `Long`'s, so that `==` +/// would compare their references. The test data looks up the numbers beyond -128 +/// and 127 on purpose, as Java caches the boxed values in that range, and +/// the references would be equal by chance. +bool ContainsByLoop( + int64_t number, + const std::vector& numbers +); + +/// \brief Check that \p numbers are unique by comparing them pair-wise. +/// +/// Java boxes the items as `Double`'s, so that `==` would compare their +/// references. +bool FloatsAreUnique( + const std::vector& numbers +); + /// \brief Check whether \p text is one of the separators. bool IsSeparator( const std::wstring& text diff --git a/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/src/verification.cpp index c91d16a07..3d4d1c0ca 100644 --- a/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/src/verification.cpp +++ b/dev/test_data/main/cpp/expected/list_of_primitives/expected_output/src/verification.cpp @@ -92,6 +92,31 @@ bool IntsAreUnique( return true; } +bool ContainsByLoop( + int64_t number, + const std::vector& numbers +) { + for (int64_t other : numbers) { + if (other == number) { + return true; + } + } + return false; +} + +bool FloatsAreUnique( + const std::vector& numbers +) { + for (size_t i = 0; i < numbers.size(); ++i) { + for (size_t j = i + (1); j < numbers.size(); ++j) { + if (numbers.at(i) == numbers.at(j)) { + return false; + } + } + } + return true; +} + bool IsSeparator( const std::wstring& text ) { @@ -304,6 +329,29 @@ bool Something_9( ); } +bool Something_10( + const void* value +) { + const types::ISomething* that = ( + static_cast(value) + ); + return !verification::ContainsByLoop( + -500, + that->some_ints() + ); +} + +bool Something_11( + const void* value +) { + const types::ISomething* that = ( + static_cast(value) + ); + return verification::FloatsAreUnique( + that->some_floats() + ); +} + /** * Give out the checks of the values of the \p shape. */ @@ -351,6 +399,14 @@ const std::vector& ChecksOf(Shape shape) { { &Something_9, L"Some strings must not be separators." + }, + { + &Something_10, + L"Some ints must not contain -500." + }, + { + &Something_11, + L"Some floats must be unique." } }; return checks; diff --git a/dev/test_data/main/csharp/expected/list_of_primitives/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/list_of_primitives/expected_output/dummy/verification.cs index b18744d3a..ffdd03b77 100644 --- a/dev/test_data/main/csharp/expected/list_of_primitives/expected_output/dummy/verification.cs +++ b/dev/test_data/main/csharp/expected/list_of_primitives/expected_output/dummy/verification.cs @@ -100,6 +100,54 @@ List numbers return true; } // public static bool IntsAreUnique + /// + /// Check whether is in , item by item. + /// + /// + /// Java boxes both and the items as Long's, so that == + /// would compare their references. The test data looks up the numbers beyond -128 + /// and 127 on purpose, as Java caches the boxed values in that range, and + /// the references would be equal by chance. + /// + public static bool ContainsByLoop( + long number, + List numbers + ) + { + foreach (var other in numbers) + { + if (other == number) + { + return true; + } + } + return false; + } // public static bool ContainsByLoop + + /// + /// Check that are unique by comparing them pair-wise. + /// + /// + /// Java boxes the items as Double's, so that == would compare their + /// references. + /// + public static bool FloatsAreUnique( + List numbers + ) + { + for (int i = 0; i < numbers.Count; i++) + { + for (int j = i + 1; j < numbers.Count; j++) + { + if (numbers[i] == numbers[j]) + { + return false; + } + } + } + return true; + } // public static bool FloatsAreUnique + /// /// Check whether is one of the separators. /// @@ -248,6 +296,20 @@ Our.ISomething that "Invariant violated:\n" + "Some strings must not be separators."); } + + if (!(!Verification.ContainsByLoop(-500, that.SomeInts))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Some ints must not contain -500."); + } + + if (!Verification.FloatsAreUnique(that.SomeFloats)) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Some floats must be unique."); + } } } // private class Transformer diff --git a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/verification/verification.go index 27ed0ad40..bea9bd7bb 100644 --- a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/verification/verification.go @@ -100,6 +100,41 @@ func IntsAreUnique( return true } +// Check whether number is in numbers, item by item. +// +// Java boxes both number and the items as `Long`'s, so that `==` +// would compare their references. The test data looks up the numbers beyond -128 +// and 127 on purpose, as Java caches the boxed values in that range, and +// the references would be equal by chance. +func ContainsByLoop( + number int64, + numbers []int64, +) bool { + for _, other := range numbers { + if other == number { + return true + } + } + return false +} + +// Check that numbers are unique by comparing them pair-wise. +// +// Java boxes the items as `Double`'s, so that `==` would compare their +// references. +func FloatsAreUnique( + numbers []float64, +) bool { + for i := 0; i < len(numbers); i++ { + for j := i + 1; j < len(numbers); j++ { + if numbers[i] == numbers[j] { + return false + } + } + } + return true +} + // Check whether text is one of the separators. func IsSeparator( text string, @@ -295,6 +330,26 @@ func VerifySomething( } } + if !(!ContainsByLoop(-500, that.SomeInts())) { + abort = onError( + newVerificationError( + "Some ints must not contain -500.",), + ) + if abort { + return + } + } + + if !FloatsAreUnique(that.SomeFloats()) { + abort = onError( + newVerificationError( + "Some floats must be unique.",), + ) + if abort { + return + } + } + if that.SomeBools() == nil { abort = onError( newVerificationError( diff --git a/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java index 21e51f113..55c6b8a05 100644 --- a/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java @@ -107,7 +107,7 @@ public static Boolean childADeclarationsAgree( return false; } wider = child; - return child.getAOnly() == childCopy.getAOnly() + return (long) child.getAOnly() == childCopy.getAOnly() && ((IChildA) wider).getAOnly() > -1000; } return true; diff --git a/dev/test_data/main/java/expected/list_of_primitives/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/list_of_primitives/expected_output/src/main/java/dummy/verification/Verification.java index bd19f880a..dea5008f6 100644 --- a/dev/test_data/main/java/expected/list_of_primitives/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/list_of_primitives/expected_output/src/main/java/dummy/verification/Verification.java @@ -37,7 +37,7 @@ public class Verification { public static Boolean isSmallPrime( Long number) { for (var prime : new java.util.ArrayList(java.util.List.of(2L, 3L, 5L, 7L))) { - if (prime == number) { + if ((long) prime == number) { return true; } } @@ -86,6 +86,43 @@ public static Boolean intsAreUnique( return true; } + /** + * Check whether {@code number} is in {@code numbers}, item by item. + * + *

Java boxes both {@code number} and the items as {@code Long}'s, so that {@code ==} + * would compare their references. The test data looks up the numbers beyond -128 + * and 127 on purpose, as Java caches the boxed values in that range, and + * the references would be equal by chance. + */ + public static Boolean containsByLoop( + Long number, + List numbers) { + for (var other : numbers) { + if ((long) other == number) { + return true; + } + } + return false; + } + + /** + * Check that {@code numbers} are unique by comparing them pair-wise. + * + *

Java boxes the items as {@code Double}'s, so that {@code ==} would compare their + * references. + */ + public static Boolean floatsAreUnique( + List numbers) { + for (int i = 0; i < numbers.size(); i++) { + for (int j = i + 1; j < numbers.size(); j++) { + if ((double) numbers.get(i) == numbers.get(j)) { + return false; + } + } + } + return true; + } + /** * Check whether {@code text} is one of the separators. */ @@ -222,6 +259,20 @@ public Stream transformSomething( "Some strings must not be separators."))); } + if (!(!containsByLoop(-500L, that.getSomeInts()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Some ints must not contain -500."))); + } + + if (!floatsAreUnique(that.getSomeFloats())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Some floats must be unique."))); + } + return errorStream; } } diff --git a/dev/test_data/main/python/expected/list_of_primitives/expected_output/dummy/verification.py b/dev/test_data/main/python/expected/list_of_primitives/expected_output/dummy/verification.py index a010f8e6e..ce8cf8908 100644 --- a/dev/test_data/main/python/expected/list_of_primitives/expected_output/dummy/verification.py +++ b/dev/test_data/main/python/expected/list_of_primitives/expected_output/dummy/verification.py @@ -111,6 +111,42 @@ def ints_are_unique( return True +def contains_by_loop( + number: int, + numbers: Sequence[int] +) -> bool: + """ + Check whether :paramref:`number` is in :paramref:`numbers`, item by item. + + Java boxes both :paramref:`number` and the items as ``Long``'s, so that ``==`` + would compare their references. The test data looks up the numbers beyond -128 + and 127 on purpose, as Java caches the boxed values in that range, and + the references would be equal by chance. + """ + # pylint: disable=all + for other in numbers: + if other == number: + return True + return False + + +def floats_are_unique( + numbers: Sequence[float] +) -> bool: + """ + Check that :paramref:`numbers` are unique by comparing them pair-wise. + + Java boxes the items as ``Double``'s, so that ``==`` would compare their + references. + """ + # pylint: disable=all + for i in range(0, len(numbers)): + for j in range(i + 1, len(numbers)): + if numbers[i] == numbers[j]: + return False + return True + + def is_separator( text: str ) -> bool: @@ -232,6 +268,16 @@ def transform_something( 'Some strings must not be separators.' ) + if not (not contains_by_loop(-500, that.some_ints)): + yield Error( + 'Some ints must not contain -500.' + ) + + if not floats_are_unique(that.some_floats): + yield Error( + 'Some floats must be unique.' + ) + _TRANSFORMER = _Transformer() diff --git a/dev/test_data/main/typescript/expected/list_of_primitives/expected_output/src/verification.ts b/dev/test_data/main/typescript/expected/list_of_primitives/expected_output/src/verification.ts index 8ba11fd66..0b220bea2 100644 --- a/dev/test_data/main/typescript/expected/list_of_primitives/expected_output/src/verification.ts +++ b/dev/test_data/main/typescript/expected/list_of_primitives/expected_output/src/verification.ts @@ -247,6 +247,49 @@ export function intsAreUnique( return true; } +/** + * Check whether `number` is in `numbers`, item by item. + * + * @remarks + * + * Java boxes both `number` and the items as `Long`'s, so that `==` + * would compare their references. The test data looks up the numbers beyond -128 + * and 127 on purpose, as Java caches the boxed values in that range, and + * the references would be equal by chance. + */ +export function containsByLoop( + number: number, + numbers: Array +): boolean { + for (const other of numbers) { + if (other == number) { + return true; + } + } + return false; +} + +/** + * Check that `numbers` are unique by comparing them pair-wise. + * + * @remarks + * + * Java boxes the items as `Double`'s, so that `==` would compare their + * references. + */ +export function floatsAreUnique( + numbers: Array +): boolean { + for (let i = 0; i < numbers.length; i++) { + for (let j = i + 1; j < numbers.length; j++) { + if (OurCommon.at(numbers, i) == OurCommon.at(numbers, j)) { + return false; + } + } + } + return true; +} + /** * Check whether `text` is one of the separators. */ @@ -392,6 +435,18 @@ class Verifier "Some strings must not be separators." ) } + + if (!(!containsByLoop(-500, that.someInts))) { + yield new VerificationError( + "Some ints must not contain -500." + ) + } + + if (!floatsAreUnique(that.someFloats)) { + yield new VerificationError( + "Some floats must be unique." + ) + } } } diff --git a/dev/test_data/parse/common_meta_models/list_of_primitives/expected_symbol_table.txt b/dev/test_data/parse/common_meta_models/list_of_primitives/expected_symbol_table.txt index fe41c321f..52af6aa45 100644 --- a/dev/test_data/parse/common_meta_models/list_of_primitives/expected_symbol_table.txt +++ b/dev/test_data/parse/common_meta_models/list_of_primitives/expected_symbol_table.txt @@ -370,6 +370,39 @@ UnverifiedSymbolTable( original_node=...), original_node=...), original_node=...)"""), + node=...), + Invariant( + description='Some ints must not contain -500.', + body=textwrap.dedent("""\ + Not( + operand=FunctionCall( + name='contains_by_loop', + args=[ + Constant( + value=-500, + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_ints', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Some floats must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='floats_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_floats', + original_node=...)], + original_node=...)"""), node=...)], serialization=None, description=None, @@ -741,6 +774,191 @@ UnverifiedSymbolTable( original_node=...)""")], node=..., arguments_by_name=...), + UnderstoodMethod( + name='contains_by_loop', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='number', + type_annotation=AtomicTypeAnnotation( + identifier='int', + node=...), + default=None, + node=...), + Argument( + name='numbers', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='int', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + For( + generator=ForEach( + variable=Name( + identifier='other', + original_node=...), + iteration=Name( + identifier='numbers', + original_node=...), + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=Comparison( + left=Name( + identifier='other', + original_node=...), + op='EQ', + right=Name( + identifier='number', + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=True, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='floats_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='numbers', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='float', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + For( + generator=ForRange( + variable=Name( + identifier='i', + original_node=...), + start=Constant( + value=0, + original_node=...), + end=FunctionCall( + name='len', + args=[ + Name( + identifier='numbers', + original_node=...)], + original_node=...), + original_node=...), + body=[ + For( + generator=ForRange( + variable=Name( + identifier='j', + original_node=...), + start=Add( + left=Name( + identifier='i', + original_node=...), + right=Constant( + value=1, + original_node=...), + original_node=...), + end=FunctionCall( + name='len', + args=[ + Name( + identifier='numbers', + original_node=...)], + original_node=...), + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=Comparison( + left=Index( + collection=Name( + identifier='numbers', + original_node=...), + index=Name( + identifier='i', + original_node=...), + original_node=...), + op='EQ', + right=Index( + collection=Name( + identifier='numbers', + original_node=...), + index=Name( + identifier='j', + original_node=...), + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=True, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), UnderstoodMethod( name='is_separator', verification=True,