Skip to content

Commit

Permalink
Fix unexpected errors when using different casings for same entity
Browse files Browse the repository at this point in the history
Ref. #562, eng/recordflux/RecordFlux#1506
  • Loading branch information
treiher committed Feb 6, 2024
1 parent d8e878f commit 26f01c8
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 68 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- Unexpected errors when using different casings for same entity (AdaCore/RecordFlux#562, eng/recordflux/RecordFlux#1506)

## [0.18.0] - 2024-01-30

### Added
Expand Down Expand Up @@ -468,6 +474,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.1.0] - 2019-05-14

[Unreleased]: https://github.com/AdaCore/RecordFlux/compare/v0.18.0...HEAD
[0.18.0]: https://github.com/AdaCore/RecordFlux/compare/v0.17.0...v0.18.0
[0.17.0]: https://github.com/AdaCore/RecordFlux/compare/v0.16.0...v0.17.0
[0.16.0]: https://github.com/AdaCore/RecordFlux/compare/v0.15.0...v0.16.0
Expand Down
2 changes: 2 additions & 0 deletions rflx/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -3455,6 +3455,8 @@ def _entity_name(expr: Expr) -> str:
expr_type = (
"variable"
if isinstance(expr, Variable)
else "literal"
if isinstance(expr, Literal)
else "function"
if isinstance(expr, Call)
else "type"
Expand Down
4 changes: 4 additions & 0 deletions rflx/model/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def __init__(
def type_identifier(self) -> ID:
return self._type_identifier

@type_identifier.setter
def type_identifier(self, identifier: ID) -> None:
self._type_identifier = identifier

@property
def type_(self) -> rty.Type:
return self._type
Expand Down
99 changes: 68 additions & 31 deletions rflx/model/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,25 +1140,25 @@ def _normalize(
"""
Normalize structure of message.
- Replace variables by literals where necessary.
- Qualify enumeration literals in conditions to prevent ambiguities.
- Add size expression for fields with implicit size. The distinction between variables and
literals is not possible in the parser, as both are syntactically identical.
- Normalize identifiers
- Add size expression for fields with implicit size
"""

def substitute(expression: expr.Expr) -> expr.Expr:
return substitute_variables(
return normalize_identifiers(
expression,
types.keys(),
self._unqualified_enum_literals,
self._qualified_enum_literals,
self._type_names,
self.package,
)

fields_map = {f.identifier: f.identifier for f in (INITIAL, *types, FINAL)}
structure = [
Link(
l.source,
l.target,
Field(ID(fields_map[l.source.identifier], location=l.source.identifier.location)),
Field(ID(fields_map[l.target.identifier], location=l.target.identifier.location)),
l.condition.substituted(substitute),
l.size.substituted(substitute),
l.first.substituted(substitute),
Expand Down Expand Up @@ -2206,6 +2206,13 @@ def __init__( # noqa: PLR0913
self.sdu = sdu
self.condition = condition

self._unqualified_enum_literals = mty.unqualified_enum_literals(
self.dependencies,
self.package,
)
self._qualified_enum_literals = mty.qualified_enum_literals(self.dependencies)
self._type_names = mty.qualified_type_names(self.dependencies)

self._normalize()

if not skip_verification:
Expand All @@ -2215,23 +2222,25 @@ def __init__( # noqa: PLR0913

def _normalize(self) -> None:
"""
Normalize condition of refinement.
Normalize refinement.
- Replace variables by literals where necessary.
- Qualify enumeration literals to prevent ambiguities.
- Set expression types
- Normalize identifiers
- Set expression types in condition
"""

unqualified_enum_literals = mty.unqualified_enum_literals(self.dependencies, self.package)
qualified_enum_literals = mty.qualified_enum_literals(self.dependencies)
type_names = mty.qualified_type_names(self.dependencies)
fields_map = {f.identifier: f.identifier for f in self.pdu.fields}

if self.field.identifier in fields_map:
self.field = Field(
ID(fields_map[self.field.identifier], location=self.field.identifier.location),
)
self.condition = self.condition.substituted(
lambda e: substitute_variables(
lambda e: normalize_identifiers(
e,
unqualified_enum_literals,
qualified_enum_literals,
type_names,
self.pdu.fields,
self._unqualified_enum_literals,
self._qualified_enum_literals,
self._type_names,
self.package,
),
)
Expand Down Expand Up @@ -2608,8 +2617,9 @@ def _merge_inner_message(
inner_message_qualified_type_names = mty.qualified_type_names(inner_message_dependencies)

def substitute_message_variables(expression: expr.Expr) -> expr.Expr:
return substitute_variables(
return normalize_identifiers(
expression,
message.fields,
message_unqualified_enum_literals,
message_qualified_enum_literals,
message_qualified_type_names,
Expand Down Expand Up @@ -3152,32 +3162,59 @@ def to_mapping(facts: Sequence[expr.Expr]) -> dict[expr.Name, expr.Expr]:
}


def substitute_variables(
def normalize_identifiers(
expression: expr.Expr,
fields: Iterable[Field],
unqualified_enum_literals: Iterable[ID],
qualified_enum_literals: Iterable[ID],
type_names: Iterable[ID],
package: ID,
) -> expr.Expr:
if isinstance(expression, expr.Variable) and expression.identifier in {
*unqualified_enum_literals,
*qualified_enum_literals,
}:
"""
Normalize identifiers.
- Unify identifier casing
- Replace variables by literals or type names where necessary
(the distinction is not possible in the parser, as the syntax is identical)
- Qualify enumeration literals to prevent ambiguities
"""
fields_map = {f.identifier: f.identifier for f in fields}
enum_literals_map = {
**{l: package * l for l in unqualified_enum_literals},
**{l: l for l in qualified_enum_literals},
}
type_names_map = {t: t for t in type_names}

if isinstance(expression, expr.Variable) and expression.identifier in fields_map:
return expr.Variable(
ID(fields_map[expression.identifier], location=expression.identifier.location),
type_=expression.type_,
location=expression.location,
)

if (
isinstance(expression, (expr.Variable, expr.Literal))
and expression.identifier in enum_literals_map
):
return expr.Literal(
package * expression.identifier
if expression.identifier in unqualified_enum_literals
else expression.identifier,
ID(
enum_literals_map[expression.identifier],
location=expression.identifier.location,
),
expression.type_,
location=expression.location,
)
if isinstance(expression, expr.Variable) and expression.identifier in type_names:

if (
isinstance(expression, (expr.Variable, expr.TypeName))
and expression.identifier in type_names
):
return expr.TypeName(
package * expression.identifier
if expression.identifier in unqualified_enum_literals
else expression.identifier,
ID(type_names_map[expression.identifier], location=expression.identifier.location),
expression.type_,
location=expression.location,
)

return expression


Expand Down
159 changes: 124 additions & 35 deletions rflx/model/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,64 +415,153 @@ def __str__(self) -> str:
def initial_state(self) -> State:
return self.states[0]

def _normalize(self) -> None:
def _normalize(self) -> None: # noqa: PLR0912
"""
Normalize all expressions of the session.
Replace variables by type names, literals or function calls without arguments where
necessary. The distinction between these different kind of expressions is not possible in
the parser, as all of these are syntactically identical.
- Unify the identifier casing.
- Replace variables by type names, literals or function calls without arguments where
necessary. The distinction between these different kind of expressions is not possible in
the parser, as all of these are syntactically identical.
"""
functions = [
p.identifier
for p in self.parameters.values()
if isinstance(p, decl.FunctionDeclaration)
]

def substitution(expression: expr.Expr) -> expr.Expr:
if isinstance(expression, expr.Variable):
if expression.identifier in self._type_names:
return expr.TypeName(
expression.identifier,
expression.type_,
location=expression.location,
)
if expression.identifier in self._enum_literals:
return expr.Literal(
expression.identifier,
expression.type_,
location=expression.location,
)
if expression.identifier in functions:
return expr.Call(
expression.identifier,
[],
expression.negative,
expression.immutable,
expression.type_,
location=expression.location,
)
return expression
channels = [
p.identifier for p in self.parameters.values() if isinstance(p, decl.ChannelDeclaration)
]
global_variables = list(self.declarations)
type_names_map = {t: t for t in self._type_names}

def normalize_identifiers_global(expression: expr.Expr) -> expr.Expr:
return normalize_identifiers(
expression,
global_variables,
self._enum_literals,
self._type_names,
functions,
)

for d in self.declarations.values():
if isinstance(d, decl.TypeCheckableDeclaration) and d.type_identifier in type_names_map:
d.type_identifier = ID(
type_names_map[d.type_identifier],
location=d.type_identifier.location,
)

if isinstance(d, decl.VariableDeclaration) and d.expression:
d.expression = d.expression.substituted(substitution)
d.expression = d.expression.substituted(normalize_identifiers_global)

states_map = {s.identifier: s.identifier for s in self.states}

for state in self.states:
variables = [*global_variables, *state.declarations]
declarations_map = {
**{v: v for v in variables},
**{c: c for c in channels},
}

def normalize_identifiers_local(
expression: expr.Expr,
variables: Iterable[ID] = variables,
) -> expr.Expr:
return normalize_identifiers(
expression,
variables,
self._enum_literals,
self._type_names,
functions,
)

for d in state.declarations.values():
if isinstance(d, decl.VariableDeclaration) and d.expression:
d.expression = d.expression.substituted(substitution)
d.expression = d.expression.substituted(normalize_identifiers_local)
for a in state.actions:
if isinstance(a, stmt.Assignment):
a.expression = a.expression.substituted(substitution)
a.expression = a.expression.substituted(normalize_identifiers_local)
if isinstance(a, stmt.AttributeStatement):
a.parameters = [p.substituted(substitution) for p in a.parameters]
if a.identifier in declarations_map:
a.identifier = ID(
declarations_map[a.identifier],
location=a.identifier.location,
)
a.parameters = [
p.substituted(normalize_identifiers_local) for p in a.parameters
]
if isinstance(a, stmt.Reset):
a.associations = {
i: e.substituted(substitution) for i, e in a.associations.items()
i: e.substituted(normalize_identifiers_local)
for i, e in a.associations.items()
}
for t in state.transitions:
t.condition = t.condition.substituted(substitution)
if t.target in states_map:
t.target = states_map[t.target]
t.condition = t.condition.substituted(normalize_identifiers_local)

if state.exception_transition and t.target in states_map:
state.exception_transition.target = states_map[state.exception_transition.target]


def normalize_identifiers(
expression: expr.Expr,
variables: Iterable[ID],
enum_literals: Iterable[ID],
type_names: Iterable[ID],
functions: Iterable[ID],
) -> expr.Expr:
variables_map = {v: v for v in variables}
type_names_map = {t: t for t in type_names}
enum_literals_map = {l: l for l in enum_literals}
functions_map = {f: f for f in functions}

if isinstance(expression, expr.Variable):
if expression.identifier in type_names_map:
return expr.TypeName(
ID(type_names_map[expression.identifier], location=expression.identifier.location),
expression.type_,
location=expression.location,
)
if expression.identifier in enum_literals_map:
return expr.Literal(
ID(
enum_literals_map[expression.identifier],
location=expression.identifier.location,
),
expression.type_,
location=expression.location,
)
if expression.identifier in functions_map:
return expr.Call(
ID(functions_map[expression.identifier], location=expression.identifier.location),
[],
expression.negative,
expression.immutable,
expression.type_,
location=expression.location,
)
if expression.identifier in variables_map:
return expr.Variable(
ID(variables_map[expression.identifier], location=expression.identifier.location),
expression.negative,
expression.immutable,
expression.type_,
location=expression.location,
)

if isinstance(expression, expr.Call) and expression.identifier in functions_map:
return expr.Call(
ID(functions_map[expression.identifier], location=expression.identifier.location),
expression.args,
expression.negative,
expression.immutable,
expression.type_,
expression.argument_types,
location=expression.location,
)

return expression


class Session(AbstractSession):
Expand Down
Loading

0 comments on commit 26f01c8

Please sign in to comment.