11import 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+
311fun 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)))
3378fun 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)
0 commit comments