// Exact, callback-free admission for the two-node escaped-computed receiver
// recursion slice. This recipe is intentionally separate from the #744
// one-function receiver plan.
///|
priv enum MutualReceiverNode {
MutualReceiverLeft
MutualReceiverRight
}
///|
#warnings("-unused_value-unused_constructor")
priv enum MutualReceiverAccessKind {
MutualReceiverStatic
MutualReceiverComputedLiteral(@token.Loc, Bool)
}
///|
#warnings("-unused_field")
priv struct MutualReceiverExpressionRecipe {
base_condition_loc : @token.Loc
marker_access : MutualReceiverAccessKind
marker_member_loc : @token.Loc
recursive_add_loc : @token.Loc
recursive_access : MutualReceiverAccessKind
recursive_member_loc : @token.Loc
recursive_call_loc : @token.Loc
recursive_subtract_loc : @token.Loc
}
///|
fn MutualReceiverExpressionRecipe::MutualReceiverExpressionRecipe(
base_condition_loc~ : @token.Loc,
marker_access~ : MutualReceiverAccessKind,
marker_member_loc~ : @token.Loc,
recursive_add_loc~ : @token.Loc,
recursive_access~ : MutualReceiverAccessKind,
recursive_member_loc~ : @token.Loc,
recursive_call_loc~ : @token.Loc,
recursive_subtract_loc~ : @token.Loc,
) -> MutualReceiverExpressionRecipe {
{
base_condition_loc,
marker_access,
marker_member_loc,
recursive_add_loc,
recursive_access,
recursive_member_loc,
recursive_call_loc,
recursive_subtract_loc,
}
}
///|
#warnings("-unused_field")
priv struct MutualReceiverNodePlan {
node : MutualReceiverNode
function_name : String
property_name : String
parameter : String
recipe : MutualReceiverExpressionRecipe
}
///|
fn MutualReceiverNodePlan::MutualReceiverNodePlan(
node~ : MutualReceiverNode,
function_name~ : String,
property_name~ : String,
parameter~ : String,
recipe~ : MutualReceiverExpressionRecipe,
) -> MutualReceiverNodePlan {
{ node, function_name, property_name, parameter, recipe }
}
///|
#warnings("-unused_field")
priv struct MutualReceiverRecursionPlan {
left : MutualReceiverNodePlan
right : MutualReceiverNodePlan
receiver_name : String
marker_name : String
marker_property_access : MutualReceiverAccessKind
left_property_access : MutualReceiverAccessKind
right_property_access : MutualReceiverAccessKind
root_access : MutualReceiverAccessKind
root_member_loc : @token.Loc
root_node : MutualReceiverNode
initial_argument : Double
}
///|
fn MutualReceiverRecursionPlan::MutualReceiverRecursionPlan(
left~ : MutualReceiverNodePlan,
right~ : MutualReceiverNodePlan,
receiver_name~ : String,
marker_name~ : String,
marker_property_access~ : MutualReceiverAccessKind,
left_property_access~ : MutualReceiverAccessKind,
right_property_access~ : MutualReceiverAccessKind,
root_access~ : MutualReceiverAccessKind,
root_member_loc~ : @token.Loc,
root_node~ : MutualReceiverNode,
initial_argument~ : Double,
) -> MutualReceiverRecursionPlan {
{
left,
right,
receiver_name,
marker_name,
marker_property_access,
left_property_access,
right_property_access,
root_access,
root_member_loc,
root_node,
initial_argument,
}
}
///|
fn mutual_receiver_access_mode_matches(
left : MutualReceiverAccessKind,
right : MutualReceiverAccessKind,
) -> Bool {
match (left, right) {
(MutualReceiverStatic, MutualReceiverStatic) => true
(
MutualReceiverComputedLiteral(_, left_escaped),
MutualReceiverComputedLiteral(_, right_escaped),
) => left_escaped == right_escaped
_ => false
}
}
///|
fn mutual_receiver_access_matches(
expected : MutualReceiverAccessKind,
actual : MutualReceiverAccessKind,
) -> Bool {
match (expected, actual) {
(MutualReceiverStatic, MutualReceiverStatic) => true
(
MutualReceiverComputedLiteral(expected_loc, expected_escaped),
MutualReceiverComputedLiteral(actual_loc, actual_escaped),
) => expected_loc == actual_loc && expected_escaped == actual_escaped
_ => false
}
}
///|
fn mutual_receiver_property_access(
property : @ast.Property,
) -> (String, MutualReceiverAccessKind)? {
guard property.kind == @ast.Init && !property.is_method && property.computed else {
return None
}
match property.key {
@ast.StringLit(name, has_escape, @token.LexForm::LexNormal, key_loc) if has_escape &&
name != "__proto__" =>
Some((name, MutualReceiverComputedLiteral(key_loc, has_escape)))
_ => None
}
}
///|
fn mutual_receiver_this_member_access(
expr : @ast.Expr,
) -> (String, MutualReceiverAccessKind, @token.Loc)? {
match expr {
@ast.ComputedMember(
@ast.ThisExpr(_),
@ast.StringLit(name, has_escape, @token.LexForm::LexNormal, key_loc),
member_loc
) if has_escape =>
Some(
(name, MutualReceiverComputedLiteral(key_loc, has_escape), member_loc),
)
_ => None
}
}
///|
fn mutual_receiver_ident_member_access(
expr : @ast.Expr,
expected_base : String,
) -> (String, MutualReceiverAccessKind, @token.Loc)? {
match expr {
@ast.ComputedMember(
@ast.Ident(base, _),
@ast.StringLit(name, has_escape, @token.LexForm::LexNormal, key_loc),
member_loc
) if base == expected_base && has_escape =>
Some(
(name, MutualReceiverComputedLiteral(key_loc, has_escape), member_loc),
)
_ => None
}
}
///|
fn mutual_receiver_recipe_access_modes_match(
plan : MutualReceiverRecursionPlan,
) -> Bool {
let mode = plan.marker_property_access
mutual_receiver_access_mode_matches(mode, plan.left_property_access) &&
mutual_receiver_access_mode_matches(mode, plan.right_property_access) &&
mutual_receiver_access_mode_matches(mode, plan.root_access) &&
mutual_receiver_access_mode_matches(mode, plan.left.recipe.marker_access) &&
mutual_receiver_access_mode_matches(mode, plan.left.recipe.recursive_access) &&
mutual_receiver_access_mode_matches(mode, plan.right.recipe.marker_access) &&
mutual_receiver_access_mode_matches(mode, plan.right.recipe.recursive_access)
}
///|
fn mutual_receiver_access_is_escaped_computed(
access : MutualReceiverAccessKind,
) -> Bool {
match access {
MutualReceiverComputedLiteral(_, true) => true
_ => false
}
}
///|
fn mutual_receiver_function_plan(
stmt : @ast.Stmt,
node : MutualReceiverNode,
receiver_name : String,
marker_name : String,
function_name : String,
property_name : String,
opposite_property_name : String,
marker_property_access : MutualReceiverAccessKind,
) -> MutualReceiverNodePlan? {
match stmt {
@ast.FuncDecl(actual_name, params, body, _, _) => {
guard actual_name == function_name &&
numeric_recursion_identifier_is_safe(actual_name) &&
params.length() == 1 &&
body.length() == 2 else {
return None
}
let parameter = params[0]
guard numeric_recursion_identifier_is_safe(parameter) &&
parameter != actual_name &&
parameter != receiver_name &&
parameter != marker_name else {
return None
}
let (marker_access, base_condition_loc, marker_member_loc) = match
body[0] {
@ast.IfStmt(
@ast.Binary(
@ast.EqEqEq,
@ast.Ident(condition_parameter, _),
base_condition,
base_condition_loc
),
@ast.ReturnStmt(Some(marker_expr), _),
None,
_
) if condition_parameter == parameter &&
exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE) =>
match mutual_receiver_this_member_access(marker_expr) {
Some((marker_property, marker_access, marker_member_loc)) if marker_property ==
marker_name &&
mutual_receiver_access_mode_matches(
marker_property_access, marker_access,
) => (marker_access, base_condition_loc, marker_member_loc)
_ => return None
}
_ => return None
}
let (
recursive_access,
recursive_member_loc,
recursive_call_loc,
recursive_add_loc,
recursive_subtract_loc,
) = match body[1] {
@ast.ReturnStmt(
Some(
@ast.Binary(
@ast.Add,
increment,
@ast.Call(recursive_expr, args, recursive_call_loc),
recursive_add_loc
)
),
_
) if exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP) => {
let (recursive_property, recursive_access, recursive_member_loc) = match
mutual_receiver_this_member_access(recursive_expr) {
Some(found) => found
None => return None
}
guard recursive_property == opposite_property_name &&
mutual_receiver_access_mode_matches(
marker_property_access, recursive_access,
) &&
args.length() == 1 else {
return None
}
match args[0] {
@ast.Binary(
@ast.Sub,
@ast.Ident(step_parameter, _),
decrement,
recursive_subtract_loc
) if step_parameter == parameter &&
exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP) =>
(
recursive_access, recursive_member_loc, recursive_call_loc, recursive_add_loc,
recursive_subtract_loc,
)
_ => return None
}
}
_ => return None
}
Some(
MutualReceiverNodePlan(
node~,
function_name~,
property_name~,
parameter~,
recipe=MutualReceiverExpressionRecipe(
base_condition_loc~,
marker_access~,
marker_member_loc~,
recursive_add_loc~,
recursive_access~,
recursive_member_loc~,
recursive_call_loc~,
recursive_subtract_loc~,
),
),
)
}
_ => None
}
}
///|
#warnings("-unused_value")
fn classify_mutual_receiver_recursion_program(
stmts : Array[@ast.Stmt],
) -> MutualReceiverRecursionPlan? {
guard stmts.length() == 4 else { return None }
let (left_name, left_params, left_body) = match stmts[0] {
@ast.FuncDecl(name, params, body, _, _) => (name, params, body)
_ => return None
}
let (right_name, right_params, right_body) = match stmts[1] {
@ast.FuncDecl(name, params, body, _, _) => (name, params, body)
_ => return None
}
guard numeric_recursion_identifier_is_safe(left_name) &&
numeric_recursion_identifier_is_safe(right_name) &&
left_name != right_name &&
left_params.length() == 1 &&
right_params.length() == 1 &&
left_body.length() == 2 &&
right_body.length() == 2 else {
return None
}
let (
receiver_name,
marker_name,
marker_property_access,
left_property_access,
right_property_access,
) = match stmts[2] {
@ast.VarDecl(
@ast.LetKind,
receiver_name,
Some(@ast.ObjectLit(properties, _)),
_
) => {
guard numeric_recursion_identifier_is_safe(receiver_name) &&
properties.length() == 3 else {
return None
}
let (marker_name, marker_property_access) = match
mutual_receiver_property_access(properties[0]) {
Some(found) => found
None => return None
}
let (left_property_name, left_property_access) = match
mutual_receiver_property_access(properties[1]) {
Some(found) => found
None => return None
}
let (right_property_name, right_property_access) = match
mutual_receiver_property_access(properties[2]) {
Some(found) => found
None => return None
}
guard left_property_name == left_name &&
right_property_name == right_name &&
exact_numeric_recursion_number(
properties[0].value,
NUMERIC_RECURSION_BASE,
) else {
return None
}
guard (match properties[1].value {
@ast.Ident(name, _) => name == left_name
_ => false
}) &&
(match properties[2].value {
@ast.Ident(name, _) => name == right_name
_ => false
}) else {
return None
}
(
receiver_name, marker_name, marker_property_access, left_property_access,
right_property_access,
)
}
_ => return None
}
guard receiver_name != left_name &&
receiver_name != right_name &&
receiver_name != marker_name &&
marker_name != left_name &&
marker_name != right_name else {
return None
}
let left_plan = match
mutual_receiver_function_plan(
stmts[0],
MutualReceiverLeft,
receiver_name,
marker_name,
left_name,
left_name,
right_name,
marker_property_access,
) {
Some(found) => found
None => return None
}
let right_plan = match
mutual_receiver_function_plan(
stmts[1],
MutualReceiverRight,
receiver_name,
marker_name,
right_name,
right_name,
left_name,
marker_property_access,
) {
Some(found) => found
None => return None
}
let (root_node, root_access, root_member_loc) = match stmts[3] {
@ast.ExprStmt(@ast.Call(root_expr, args, _), _) if args.length() == 1 => {
let (root_property, root_access, root_member_loc) = match
mutual_receiver_ident_member_access(root_expr, receiver_name) {
Some(found) => found
None => return None
}
let root_node = if root_property == left_name {
MutualReceiverLeft
} else if root_property == right_name {
MutualReceiverRight
} else {
return None
}
guard mutual_receiver_access_mode_matches(
marker_property_access, root_access,
) &&
exact_numeric_recursion_number(
args[0],
NUMERIC_RECURSION_INITIAL_ARGUMENT,
) else {
return None
}
(root_node, root_access, root_member_loc)
}
_ => return None
}
Some(
MutualReceiverRecursionPlan(
left=left_plan,
right=right_plan,
receiver_name~,
marker_name~,
marker_property_access~,
left_property_access~,
right_property_access~,
root_access~,
root_member_loc~,
root_node~,
initial_argument=NUMERIC_RECURSION_INITIAL_ARGUMENT,
),
)
}
///|
fn mutual_receiver_nodes_match(
left : MutualReceiverNode,
right : MutualReceiverNode,
) -> Bool {
match (left, right) {
(MutualReceiverLeft, MutualReceiverLeft)
| (MutualReceiverRight, MutualReceiverRight) => true
_ => false
}
}
///|
fn mutual_receiver_expression_recipe_matches(
expected : MutualReceiverExpressionRecipe,
actual : MutualReceiverExpressionRecipe,
) -> Bool {
expected.base_condition_loc == actual.base_condition_loc &&
mutual_receiver_access_matches(expected.marker_access, actual.marker_access) &&
expected.marker_member_loc == actual.marker_member_loc &&
expected.recursive_add_loc == actual.recursive_add_loc &&
mutual_receiver_access_matches(
expected.recursive_access,
actual.recursive_access,
) &&
expected.recursive_member_loc == actual.recursive_member_loc &&
expected.recursive_call_loc == actual.recursive_call_loc &&
expected.recursive_subtract_loc == actual.recursive_subtract_loc
}
///|
fn mutual_receiver_node_plans_match(
expected : MutualReceiverNodePlan,
actual : MutualReceiverNodePlan,
) -> Bool {
mutual_receiver_nodes_match(expected.node, actual.node) &&
expected.function_name == actual.function_name &&
expected.property_name == actual.property_name &&
expected.parameter == actual.parameter &&
mutual_receiver_expression_recipe_matches(expected.recipe, actual.recipe)
}
///|
fn mutual_receiver_recursion_plans_match(
expected : MutualReceiverRecursionPlan,
actual : MutualReceiverRecursionPlan,
) -> Bool {
expected.receiver_name == actual.receiver_name &&
expected.marker_name == actual.marker_name &&
mutual_receiver_access_matches(
expected.marker_property_access,
actual.marker_property_access,
) &&
mutual_receiver_access_matches(
expected.left_property_access,
actual.left_property_access,
) &&
mutual_receiver_access_matches(
expected.right_property_access,
actual.right_property_access,
) &&
mutual_receiver_access_matches(expected.root_access, actual.root_access) &&
expected.root_member_loc == actual.root_member_loc &&
mutual_receiver_nodes_match(expected.root_node, actual.root_node) &&
expected.initial_argument == actual.initial_argument &&
mutual_receiver_node_plans_match(expected.left, actual.left) &&
mutual_receiver_node_plans_match(expected.right, actual.right)
}
///|
#warnings("-unused_value")
fn mutual_receiver_root_statement_matches(
plan : MutualReceiverRecursionPlan,
index : Int,
stmt : @ast.Stmt,
) -> Bool {
match index {
0 =>
match
mutual_receiver_function_plan(
stmt,
MutualReceiverLeft,
plan.receiver_name,
plan.marker_name,
plan.left.function_name,
plan.left.property_name,
plan.right.property_name,
plan.marker_property_access,
) {
Some(found) => mutual_receiver_node_plans_match(plan.left, found)
None => false
}
1 =>
match
mutual_receiver_function_plan(
stmt,
MutualReceiverRight,
plan.receiver_name,
plan.marker_name,
plan.right.function_name,
plan.right.property_name,
plan.left.property_name,
plan.marker_property_access,
) {
Some(found) => mutual_receiver_node_plans_match(plan.right, found)
None => false
}
2 =>
match stmt {
@ast.VarDecl(
@ast.LetKind,
receiver_name,
Some(@ast.ObjectLit(properties, _)),
_
) => {
guard receiver_name == plan.receiver_name && properties.length() == 3 else {
return false
}
let (marker_name, marker_access) = match
mutual_receiver_property_access(properties[0]) {
Some(found) => found
None => return false
}
let (left_name, left_access) = match
mutual_receiver_property_access(properties[1]) {
Some(found) => found
None => return false
}
let (right_name, right_access) = match
mutual_receiver_property_access(properties[2]) {
Some(found) => found
None => return false
}
marker_name == plan.marker_name &&
left_name == plan.left.property_name &&
right_name == plan.right.property_name &&
mutual_receiver_access_matches(
plan.marker_property_access,
marker_access,
) &&
mutual_receiver_access_matches(plan.left_property_access, left_access) &&
mutual_receiver_access_matches(
plan.right_property_access,
right_access,
) &&
exact_numeric_recursion_number(
properties[0].value,
NUMERIC_RECURSION_BASE,
) &&
(match properties[1].value {
@ast.Ident(name, _) => name == plan.left.function_name
_ => false
}) &&
(match properties[2].value {
@ast.Ident(name, _) => name == plan.right.function_name
_ => false
})
}
_ => false
}
3 =>
match stmt {
@ast.ExprStmt(@ast.Call(root_expr, args, _), _) if args.length() == 1 => {
let (property_name, root_access, root_member_loc) = match
mutual_receiver_ident_member_access(root_expr, plan.receiver_name) {
Some(found) => found
None => return false
}
let expected_property = match plan.root_node {
MutualReceiverLeft => plan.left.property_name
MutualReceiverRight => plan.right.property_name
}
property_name == expected_property &&
mutual_receiver_access_matches(plan.root_access, root_access) &&
root_member_loc == plan.root_member_loc &&
exact_numeric_recursion_number(args[0], plan.initial_argument)
}
_ => false
}
_ => false
}
}
///|
fn mutual_receiver_body_statement_matches(
plan : MutualReceiverRecursionPlan,
node : MutualReceiverNode,
index : Int,
stmt : @ast.Stmt,
) -> Bool {
let selected = match node {
MutualReceiverLeft => plan.left
MutualReceiverRight => plan.right
}
match index {
0 =>
match stmt {
@ast.IfStmt(
@ast.Binary(
@ast.EqEqEq,
@ast.Ident(parameter, _),
base_condition,
base_condition_loc
),
@ast.ReturnStmt(Some(marker_expr), _),
None,
_
) => {
let (marker_name, marker_access, marker_member_loc) = match
mutual_receiver_this_member_access(marker_expr) {
Some(found) => found
None => return false
}
parameter == selected.parameter &&
marker_name == plan.marker_name &&
mutual_receiver_access_matches(
selected.recipe.marker_access,
marker_access,
) &&
base_condition_loc == selected.recipe.base_condition_loc &&
marker_member_loc == selected.recipe.marker_member_loc &&
exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE)
}
_ => false
}
1 => {
let opposite = match node {
MutualReceiverLeft => plan.right.property_name
MutualReceiverRight => plan.left.property_name
}
match stmt {
@ast.ReturnStmt(
Some(
@ast.Binary(
@ast.Add,
increment,
@ast.Call(recursive_expr, args, recursive_call_loc),
recursive_add_loc
)
),
_
) => {
let (property_name, recursive_access, recursive_member_loc) = match
mutual_receiver_this_member_access(recursive_expr) {
Some(found) => found
None => return false
}
guard property_name == opposite && args.length() == 1 else {
return false
}
guard mutual_receiver_access_matches(
selected.recipe.recursive_access,
recursive_access,
) &&
recursive_add_loc == selected.recipe.recursive_add_loc &&
recursive_member_loc == selected.recipe.recursive_member_loc &&
recursive_call_loc == selected.recipe.recursive_call_loc &&
exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP) else {
return false
}
match args[0] {
@ast.Binary(
@ast.Sub,
@ast.Ident(parameter, _),
decrement,
recursive_subtract_loc
) =>
parameter == selected.parameter &&
recursive_subtract_loc == selected.recipe.recursive_subtract_loc &&
exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP)
_ => false
}
}
_ => false
}
}
_ => false
}
}
///|
#warnings("-unused_value")
fn mutual_receiver_recursion_plan_is_dispatchable(
plan : MutualReceiverRecursionPlan,
) -> Bool {
mutual_receiver_recipe_access_modes_match(plan) &&
mutual_receiver_access_is_escaped_computed(plan.marker_property_access) &&
mutual_receiver_access_is_escaped_computed(plan.left_property_access) &&
mutual_receiver_access_is_escaped_computed(plan.right_property_access) &&
mutual_receiver_access_is_escaped_computed(plan.root_access) &&
mutual_receiver_access_is_escaped_computed(plan.left.recipe.marker_access) &&
mutual_receiver_access_is_escaped_computed(plan.left.recipe.recursive_access) &&
mutual_receiver_access_is_escaped_computed(plan.right.recipe.marker_access) &&
mutual_receiver_access_is_escaped_computed(plan.right.recipe.recursive_access)
}