Skip to content

Commit

Permalink
[V3] Use type strengthening for AASd-117 (#261)
Browse files Browse the repository at this point in the history
We use type strengthening instead of invariants so that compilers can
detect null exceptions at compile time.
  • Loading branch information
mristin committed Mar 21, 2023
1 parent 3191ea5 commit 02712de
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 56 deletions.
48 changes: 20 additions & 28 deletions aas_core_meta/v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -1694,16 +1694,8 @@ def __init__(
self.description = description


# fmt: off
@abstract
@reference_in_the_book(section=(5, 3, 2, 7))
@invariant(
lambda self:
self.ID_short is not None,
"Constraint AASd-117: ID-short of Referables not being a direct child of "
"a Submodel element list shall be specified."
)
# fmt: on
class Identifiable(Referable):
"""An element that has a globally unique identifier."""

Expand All @@ -1720,12 +1712,28 @@ class Identifiable(Referable):
ID: "Identifier"
"""The globally unique identification of the element."""

ID_short: "ID_short_type"
"""
In case of identifiables this attribute is a short name of the element.
In case of referable this ID is an identifying string of the element within
its name space.
.. note::
In case the element is a property and the property has a semantic definition
(:attr:`Has_semantics.semantic_ID`) conformant to IEC61360
the :attr:`ID_short` is typically identical to the short name in English.
:attr:`ID_short` is strengthened to required in this class,
see :constraintref:`AASd-117`.
"""

def __init__(
self,
ID_short: ID_short_type,
ID: "Identifier",
extensions: Optional[List["Extension"]] = None,
category: Optional[Name_type] = None,
ID_short: Optional[ID_short_type] = None,
display_name: Optional[List["Lang_string_name_type"]] = None,
description: Optional[List["Lang_string_text_type"]] = None,
administration: Optional["Administrative_information"] = None,
Expand Down Expand Up @@ -2115,11 +2123,11 @@ class Asset_administration_shell(Identifiable, Has_data_specification):

def __init__(
self,
ID_short: ID_short_type,
ID: Identifier,
asset_information: "Asset_information",
extensions: Optional[List["Extension"]] = None,
category: Optional[Name_type] = None,
ID_short: Optional[ID_short_type] = None,
display_name: Optional[List["Lang_string_name_type"]] = None,
description: Optional[List["Lang_string_text_type"]] = None,
administration: Optional["Administrative_information"] = None,
Expand Down Expand Up @@ -2477,10 +2485,10 @@ class Submodel(

def __init__(
self,
ID_short: ID_short_type,
ID: Identifier,
extensions: Optional[List["Extension"]] = None,
category: Optional[Name_type] = None,
ID_short: Optional[ID_short_type] = None,
display_name: Optional[List["Lang_string_name_type"]] = None,
description: Optional[List["Lang_string_text_type"]] = None,
administration: Optional["Administrative_information"] = None,
Expand Down Expand Up @@ -2577,15 +2585,7 @@ def __init__(
)


# fmt: off
@reference_in_the_book(section=(5, 3, 7, 15))
@invariant(
lambda self:
self.ID_short is not None,
"Constraint AASd-117: ID-short of Referables not being a direct child of a "
"Submodel element list shall be specified."
)
# fmt: on
class Relationship_element(Submodel_element):
"""
A relationship element is used to define a relationship between two elements
Expand Down Expand Up @@ -3732,16 +3732,8 @@ def __init__(
self.payload = payload


# fmt: off
@abstract
@reference_in_the_book(section=(5, 3, 7, 8))
@invariant(
lambda self:
self.ID_short is not None,
"Constraint AASd-117: ID-short of Referables not being a direct child of "
"a Submodel element list shall be specified."
)
# fmt: on
class Event_element(Submodel_element):
"""
An event element.
Expand Down Expand Up @@ -4433,10 +4425,10 @@ class Concept_description(Identifiable, Has_data_specification):

def __init__(
self,
ID_short: ID_short_type,
ID: Identifier,
extensions: Optional[List["Extension"]] = None,
category: Optional[Name_type] = None,
ID_short: Optional[ID_short_type] = None,
display_name: Optional[List["Lang_string_name_type"]] = None,
description: Optional[List["Lang_string_text_type"]] = None,
administration: Optional["Administrative_information"] = None,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"black==22.3.0",
"mypy==0.910",
"asttokens>=2.0.8,<3",
"aas-core-codegen@git+https://github.com/aas-core-works/aas-core-codegen@c6304a4#egg=aas-core-codegen",
"aas-core-codegen@git+https://github.com/aas-core-works/aas-core-codegen@168c84e#egg=aas-core-codegen",
"astpretty==3.0.0",
"pygments>=2,<3"
],
Expand Down
41 changes: 14 additions & 27 deletions tests/test_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -1469,11 +1469,6 @@ def test_constraint_117_on_non_submodel_element(self) -> None:

errors = [] # type: List[str]

# NOTE (mristin, 2023-03-17):
# If the ancestor class defines the constraint on ID-short, we do not have to
# repeat it in the descendants.
descendants_to_skip_id_set = set() # type: Set[int]

for our_type in symbol_table.our_types_topologically_sorted:
if not isinstance(
our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
Expand All @@ -1498,33 +1493,25 @@ def test_constraint_117_on_non_submodel_element(self) -> None:
if not our_type.is_subclass_of(referable_cls):
continue

# NOTE (mristin, 2023-03-17):
# If the ancestor class defines the constraint on ID-short, we do not have to
# repeat it in the descendants.
if id(our_type) in descendants_to_skip_id_set:
# NOTE (mristin, 2023-03-21):
# We use type strengthening to implement 117.
if Identifier("ID_short") not in our_type.properties_by_name:
errors.append(
f"Expected the referable class {our_type.name!r} to define "
f"the property ID_short, but it does not. See Constraint AASd-117."
)
continue

expected_condition_str = "self.ID_short is not None"
expected_condition = tests.common.parse_condition(expected_condition_str)

if not tests.common.has_invariant(
expected_condition=expected_condition,
expected_description=expected_description,
invariants=our_type.invariants,
id_short_prop = our_type.properties_by_name[Identifier("ID_short")]
if isinstance(
id_short_prop.type_annotation, intermediate.OptionalTypeAnnotation
):
errors.append(
f"The invariant corresponding to Constraint AASd-117 is "
f"expected in the class {our_type.name!r} "
f"which inherits from {referable_cls.name!r}, "
f"but it could not be found.\n"
f"\n"
f"Expected condition of the invariant was:\n"
f"{expected_condition_str}\n\n"
f"Expected description was:\n"
f"{expected_description}"
f"Expected the referable class {our_type.name!r} to define "
f"the property ID_short as required, but it defines it "
f"as {id_short_prop.type_annotation}. See Constraint AASd-117."
)

descendants_to_skip_id_set.update(our_type.descendant_id_set)
continue

if len(errors) > 0:
errors_joined = "\n".join(tests.common.make_bullet_points(errors))
Expand Down

0 comments on commit 02712de

Please sign in to comment.