Skip to content

Commit 83e872d

Browse files
committed
Auto-fix: Add support for 'use_memberships'
Also enhance the rule detection.
1 parent c5fd174 commit 83e872d

File tree

4 files changed

+108
-16
lines changed

4 files changed

+108
-16
lines changed

lkql_checker/share/lkql/use_memberships.lkql

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import stdlib
22

3+
fun get_first_id(node) =
4+
|" Get the first Identifier in the given ``node`` which is, either the
5+
|" left part of a RelationOp, or the expression if a MembershipExpr
6+
match (from node select first (RelationOp(f_left: Identifier) |
7+
MembershipExpr(f_expr: Identifier)))
8+
| r@RelationOp => r.f_left
9+
| m@MembershipExpr => m.f_expr
10+
311
fun check_expr(expr, id, short_circuit) =
412
match expr
513
# Strip parenthesis
614
| ParenExpr => check_expr(expr.f_expr, id, short_circuit)
15+
716
# Check both "or" operands
8-
| BinOp(f_op: OpOr | OpOrElse when short_circuit) =>
17+
| BinOp(
18+
f_op: op@(OpOr | OpOrElse when short_circuit)
19+
when stdlib.is_predefined_op(op)
20+
) =>
921
check_expr(expr.f_left, id, short_circuit) and
1022
check_expr(expr.f_right, id, short_circuit)
23+
1124
# Allow X >= E1 and X <= E2
12-
| BinOp(f_op: (op@OpAnd when stdlib.is_predefined_op(op)) |
13-
OpAndThen when short_circuit) =>
25+
| BinOp(
26+
f_op: op@(OpAnd | OpAndThen when short_circuit)
27+
when stdlib.is_predefined_op(op)
28+
) =>
1429
expr.f_left is RelationOp(
1530
f_op: gte@OpGte,
1631
f_left: Identifier(p_name_matches(id): true)) when
@@ -19,17 +34,47 @@ fun check_expr(expr, id, short_circuit) =
1934
f_left: Identifier(p_name_matches(id): true)) when
2035
stdlib.is_predefined_op(gte) and
2136
stdlib.is_predefined_op(lte)
37+
2238
# Allow X = C
2339
| RelationOp(f_op: op@OpEq) =>
2440
expr.f_left is Name(p_name_matches(id): true) and
2541
stdlib.is_predefined_op(op)
42+
2643
# Allow X in ...
2744
| MembershipExpr(f_op: OpIn) => expr.f_expr.p_name_matches(id)
45+
2846
# Reject anything else
2947
| * => false
3048

49+
fun to_membership_exprs(node) =
50+
|" Used by ``membership_equivalent`` to compute the membership format
51+
|" of an expression. This function returns a list containing all
52+
|" required parts for the created membership expression.
53+
match node
54+
| ParenExpr => to_membership_exprs(node.f_expr)
55+
| BinOp(f_op: (OpOr | OpOrElse)) =>
56+
to_membership_exprs(node.f_left) & to_membership_exprs(node.f_right)
57+
| BinOp(f_op: (OpAnd | OpAndThen)) =>
58+
[new BinOp(
59+
f_left=node.f_left.f_right,
60+
f_op= new OpDoubleDot(),
61+
f_right=node.f_right.f_right
62+
)]
63+
| RelationOp(f_op: OpEq) => [node.f_right]
64+
| MembershipExpr => node.f_membership_exprs.children
65+
66+
fun membership_equivalent(bin_op) =
67+
|" Get the membership equivalent of a BinOp ``bin_op`` which has been
68+
|" flagged by the "use_memberships" rule.
69+
new MembershipExpr(
70+
f_expr=get_first_id(bin_op),
71+
f_op=new OpIn(),
72+
f_membership_exprs=new ExprAlternativesList(to_membership_exprs(bin_op))
73+
)
74+
3175
@check(message="expression may be replaced by a membership test",
32-
category="Style", subcategory="Programming Practice")
76+
category="Style", subcategory="Programming Practice",
77+
auto_fix=(n, ctx) => ctx.replace(n, membership_equivalent(n)))
3378
fun use_memberships(node, short_circuit = false) =
3479
|" Flag expressions that could be rewritten as membership tests. Only expressions
3580
|" that are not subexpressions of other expressions are flagged. An expression
@@ -69,14 +114,10 @@ fun use_memberships(node, short_circuit = false) =
69114
|" A = B
70115
|" or
71116
|" A = B + A;
72-
node is BinOp(parent: not Expr,
73-
f_op: (op@OpOr when stdlib.is_predefined_op(op)) |
74-
OpOrElse when short_circuit)
75-
# Find a first variable used as LHS (id) and pass it to check_expr
76-
and match (from node select first
77-
(RelationOp(f_left: Identifier)
78-
| MembershipExpr(f_expr: Identifier)))
79-
| r@RelationOp => check_expr(node, r.f_left, short_circuit)
80-
| m@MembershipExpr => check_expr(node, m.f_expr, short_circuit)
81-
| * => false
117+
node is BinOp(
118+
parent: not Expr,
119+
f_op: op@(OpOr | OpAnd | (OpOrElse | OpAndThen) when short_circuit)
120+
) when stdlib.is_predefined_op(op)
82121

122+
# Find a first variable used as LHS (id) and pass it to check_expr
123+
and check_expr(node, get_first_id(node), short_circuit)

testsuite/tests/checks/use_memberships/member.adb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ begin
3737
end;
3838

3939
Bool := A = B or (A >= 1 and A <= B); -- FLAG
40+
Bool := A >= 1 and A <= B; -- FLAG
4041
Bool := A = 100 or A in S; -- FLAG
4142
Bool := A = 100 or A in 1 .. B; -- FLAG
4243
end Member;

testsuite/tests/checks/use_memberships/test.out

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,59 @@ member.adb:39:12: rule violation: expression may be replaced by a membership tes
1818
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1919

2020
member.adb:40:12: rule violation: expression may be replaced by a membership test
21-
40 | Bool := A = 100 or A in S; -- FLAG
21+
40 | Bool := A >= 1 and A <= B; -- FLAG
2222
| ^^^^^^^^^^^^^^^^^
2323

2424
member.adb:41:12: rule violation: expression may be replaced by a membership test
25-
41 | Bool := A = 100 or A in 1 .. B; -- FLAG
25+
41 | Bool := A = 100 or A in S; -- FLAG
26+
| ^^^^^^^^^^^^^^^^^
27+
28+
member.adb:42:12: rule violation: expression may be replaced by a membership test
29+
42 | Bool := A = 100 or A in 1 .. B; -- FLAG
2630
| ^^^^^^^^^^^^^^^^^^^^^^
2731

32+
Patched "member.adb":
33+
=====================
34+
35+
procedure Member (A, B : Integer) is
36+
Bool : Boolean;
37+
subtype S is Integer range 1 .. B;
38+
begin
39+
if A in 0 -- FLAG
40+
|Natural
41+
|2
42+
|3 ..5 then
43+
null;
44+
end if;
45+
46+
if A in 0 -- FLAG if short_circuit
47+
|3 ..5 then
48+
null;
49+
end if;
50+
51+
if A = 0 or (A in Natural and A = 2) then -- NOFLAG
52+
null;
53+
end if;
54+
55+
if A = 0 -- NOFLAG
56+
or (A >= 3 and A < 5)
57+
then
58+
null;
59+
end if;
60+
61+
declare
62+
function "or" (A, B : Boolean) return Boolean is (True);
63+
begin
64+
if A = 0 -- NOFLAG
65+
or A = 2
66+
then
67+
null;
68+
end if;
69+
end;
70+
71+
Bool := A in B |1 ..B; -- FLAG
72+
Bool := A in 1 ..B; -- FLAG
73+
Bool := A in 100 |S; -- FLAG
74+
Bool := A in 100 |1 .. B; -- FLAG
75+
end Member;
76+

testsuite/tests/checks/use_memberships/test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ rule_name: use_memberships
33
project: 'prj.gpr'
44
rule_arguments:
55
Use_Memberships.short_circuit: 'true'
6+
auto_fix: True

0 commit comments

Comments
 (0)