Skip to content

Commit

Permalink
Merge branch 'topic/1274' into 'master'
Browse files Browse the repository at this point in the history
Consider all parts of entity when looking for an aspect

Closes #1274

See merge request eng/libadalang/libadalang!1546
  • Loading branch information
thvnx committed Feb 22, 2024
2 parents 2876d0e + cf19b58 commit 3730e4c
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 30 deletions.
98 changes: 68 additions & 30 deletions ada/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2699,20 +2699,19 @@ def library_item_pragmas():

@langkit_property(return_type=Aspect, public=True,
dynamic_vars=[default_imprecise_fallback()])
def get_aspect(name=Symbol):
def get_aspect(name=Symbol, previous_parts_only=(Bool, False)):
"""
Return the aspect with name ``name`` associated to this entity.

Aspects are properties of entities that can be specified by the Ada
program, either via aspect specifications, pragmas, or attributes.

This will return the syntactic node corresponding to attribute
directly.

Note: for some aspects (e.g. Inline), Libadalang will check if they are
defined on any part of the entity.
directly. See ``DefiningName.P_Get_Aspect`` for more details.
"""
return Entity.defining_name_or_raise._.get_aspect(name)
return Entity.defining_name_or_raise._.get_aspect(
name, previous_parts_only
)

@langkit_property(return_type=Bool, public=True,
dynamic_vars=[default_imprecise_fallback()])
Expand Down Expand Up @@ -3520,7 +3519,14 @@ def basic_decl_next_part_for_decl():
# A body is not expected if the library level
# declaration is an imported subprogram.
And(b.is_a(T.BasicSubpDecl, T.GenericSubpDecl),
b.as_entity.has_aspect("Import"))
# Now that get_aspect looks in all parts, we must
# not call has_aspect("Import") from here to avoid
# infinite recursion.
Not(b.as_entity.then(
lambda e: e.get_aspect_assoc("Import")._or(
e.get_pragma("Import")
)
).is_null))
))
)
)
Expand Down Expand Up @@ -18113,9 +18119,43 @@ def get_aspect_impl(name=Symbol, inherited=Bool):
value=atc.expr, inherited=inherited)
),
No(Aspect)
))._or(
# If nothing has been found so far, check out for any inherited
# aspect.
))

@langkit_property(return_type=Aspect,
dynamic_vars=[default_imprecise_fallback()])
def get_aspect_on_parts(name=Symbol, inherited=Bool,
previous_parts_only=Bool):
"""
Return the aspect with name ``name`` associated to entity that this
name defines.

First, check for aspect on all parts of entity (``previous_parts_only``
can be used to restrict the search to entity and its previous part to
comply with visibility rules).

If no aspect if found on entity, recursively check for it on its
parents.
"""
parts_to_check = Var(Cond(
# SPARK_Mode has its own logic (see `is_spark`). For instance, if
# defined on a body, it doesn't apply to the corresponding
# specification, and conversely. Only consider the current part
# when looking for it.
name == 'SPARK_Mode',
Entity.singleton,

previous_parts_only,
Entity.singleton.concat(Entity.all_previous_parts),

Entity.all_parts
))
return parts_to_check.map(
lambda p: p.get_aspect_impl(name, inherited)
).find(
lambda a: a.exists
)._or(
# If nothing has been found so far for entity, check out for any
# inherited aspect.
Entity.basic_decl_no_internal.cast(BaseTypeDecl).then(
lambda bd: Let(
lambda typ=If(bd.is_a(T.BaseSubtypeDecl),
Expand All @@ -18124,15 +18164,18 @@ def get_aspect_impl(name=Symbol, inherited=Bool):
If(
Or(typ.is_null, typ == bd),
No(T.Aspect),
typ.name.get_aspect_impl(name, inherited=True)
typ.name.get_aspect_on_parts(
name, inherited=True,
previous_parts_only=previous_parts_only
)
)
)
)
)

@langkit_property(return_type=Aspect, public=True,
dynamic_vars=[default_imprecise_fallback()])
def get_aspect(name=Symbol):
def get_aspect(name=Symbol, previous_parts_only=(Bool, False)):
"""
Return the aspect with name ``name`` associated to entity that this
name defines.
Expand All @@ -18143,25 +18186,20 @@ def get_aspect(name=Symbol):
This will return the syntactic node corresponding to attribute
directly.

Note: for some aspects (e.g. ``Inline``), Libadalang will check if they
are defined on any part of the entity.
Note: by default, Libadalang will check if the aspect is defined on any
part of the entity. However, the ``previous_parts_only`` parameter can
be set to True to limit the search to the current entity and its
previous parts in order to comply with visibilily rules. That way, if
an aspect is defined on the private part of a type, calling this
property on its corresponding public view won't return the aspect
unlike the call on the private view.

Morover, since aspects can be inherited, if none was found for the
current entity, Libadalang will also search for the aspect on the
parents of entity (in that case the ``inherited`` field will be set
to ``True`` in the returned result).
"""
parts_to_check = Var(If(
name.any_of(
'Annotate', 'Inline', 'Obsolescent',
# For the following aspects, an aspect only on the body is
# illegal, but we don't care about illegal cases, and this
# allows us to auto propagate the aspect from spec to body.
'Ghost', 'Default_Initial_Condition'
),
Entity.all_parts,
Entity.singleton
))
return parts_to_check.map(
lambda p: p.get_aspect_impl(name, False)
).find(
lambda a: a.exists
)
return Entity.get_aspect_on_parts(name, False, previous_parts_only)

@langkit_property(return_type=Bool, public=True,
dynamic_vars=[default_imprecise_fallback()])
Expand Down
35 changes: 35 additions & 0 deletions testsuite/tests/properties/get_aspect_6/test.adb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
procedure Test is
package P is
type T1 is private;
--% node.p_get_aspect('String_Literal')
--% node.p_get_aspect('String_Literal', True)

type T2 is private with String_Literal => SLT2;
--% node.p_get_aspect('String_Literal')
--% node.p_get_aspect('String_Literal', True)
function SLT2 (X : Wide_Wide_String) return T2;
private
type T1 is null record with String_Literal => SLT1;
--% node.p_get_aspect('String_Literal')
--% node.p_get_aspect('String_Literal', True)
function SLT1 (X : Wide_Wide_String) return T1;

type T2 is null record;
--% node.p_get_aspect('String_Literal')
--% node.p_get_aspect('String_Literal', True)
end P;

package body P is
GT1 : T1;
GT2 : T2;

function SLT1 (X : Wide_Wide_String) return T1 is (GT1);
function SLT2 (X : Wide_Wide_String) return T2 is (GT2);

type T3 is new T2;
--% node.p_get_aspect('String_Literal')
--% node.p_get_aspect('String_Literal', True)
end P;
begin
null;
end Test;
44 changes: 44 additions & 0 deletions testsuite/tests/properties/get_aspect_6/test.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Working on node <ConcreteTypeDecl ["T1"] test.adb:3:7-3:26>
===========================================================

Eval 'node.p_get_aspect('String_Literal')'
Result: <Aspect exists=True node=<AspectAssoc test.adb:12:35-12:57> value=<Id "SLT1" test.adb:12:53-12:57> inherited=False>

Eval 'node.p_get_aspect('String_Literal', True)'
Result: <Aspect exists=False node=None value=None inherited=False>

Working on node <ConcreteTypeDecl ["T2"] test.adb:7:7-7:54>
===========================================================

Eval 'node.p_get_aspect('String_Literal')'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=False>

Eval 'node.p_get_aspect('String_Literal', True)'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=False>

Working on node <ConcreteTypeDecl ["T1"] test.adb:12:7-12:58>
=============================================================

Eval 'node.p_get_aspect('String_Literal')'
Result: <Aspect exists=True node=<AspectAssoc test.adb:12:35-12:57> value=<Id "SLT1" test.adb:12:53-12:57> inherited=False>

Eval 'node.p_get_aspect('String_Literal', True)'
Result: <Aspect exists=True node=<AspectAssoc test.adb:12:35-12:57> value=<Id "SLT1" test.adb:12:53-12:57> inherited=False>

Working on node <ConcreteTypeDecl ["T2"] test.adb:17:7-17:30>
=============================================================

Eval 'node.p_get_aspect('String_Literal')'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=False>

Eval 'node.p_get_aspect('String_Literal', True)'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=False>

Working on node <ConcreteTypeDecl ["T3"] test.adb:29:7-29:25>
=============================================================

Eval 'node.p_get_aspect('String_Literal')'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=True>

Eval 'node.p_get_aspect('String_Literal', True)'
Result: <Aspect exists=True node=<AspectAssoc test.adb:7:31-7:53> value=<Id "SLT2" test.adb:7:49-7:53> inherited=True>
2 changes: 2 additions & 0 deletions testsuite/tests/properties/get_aspect_6/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
driver: inline-playground
input_sources: [test.adb]

0 comments on commit 3730e4c

Please sign in to comment.