Skip to content

Commit 1df8d73

Browse files
olwangclaude
andcommitted
refactor(checks): split check_call_args into per-phase validators (behavior-preserving)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1da30f8 commit 1df8d73

1 file changed

Lines changed: 162 additions & 90 deletions

File tree

crates/rsscript/src/checks/calls.rs

Lines changed: 162 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,15 +1102,109 @@ fn check_call_args(
11021102
.map(|param| param.name.clone())
11031103
.collect();
11041104

1105-
for arg in args {
1106-
if arg.name.is_none()
1107-
&& !allow_positional_args
1108-
&& constructor_field_shorthand_name(
1109-
allow_constructor_field_shorthand,
1105+
// Resolve each argument's target parameter name exactly once and reuse it
1106+
// across every per-arg validation phase below. The resolution depends only on
1107+
// the argument, its position, and these fixed inputs, so it is identical in
1108+
// each phase — sharing it does not change which diagnostics fire or in what
1109+
// order.
1110+
let resolved_names: Vec<Option<&str>> = args
1111+
.iter()
1112+
.enumerate()
1113+
.map(|(index, arg)| {
1114+
resolved_arg_param_name(
11101115
arg,
1116+
index,
1117+
allow_constructor_field_shorthand,
1118+
allow_positional_args,
11111119
&param_names,
1120+
&signature_params,
11121121
)
1113-
.is_none()
1122+
})
1123+
.collect();
1124+
1125+
check_argument_naming(
1126+
analyzer,
1127+
args,
1128+
&call_name,
1129+
allow_positional_args,
1130+
allow_constructor_field_shorthand,
1131+
&param_names,
1132+
&signature_params,
1133+
&resolved_names,
1134+
);
1135+
check_argument_completeness(
1136+
analyzer,
1137+
&call_name,
1138+
call_span,
1139+
&signature_params,
1140+
&resolved_names,
1141+
);
1142+
check_argument_effects(
1143+
analyzer,
1144+
args,
1145+
&call_name,
1146+
allow_positional_args,
1147+
&param_effects,
1148+
&resolved_names,
1149+
);
1150+
1151+
let type_param_substitutions =
1152+
call_type_param_substitutions(analyzer, Some(function), callee, args, signature);
1153+
check_generic_call_bounds(
1154+
analyzer,
1155+
function,
1156+
callee,
1157+
&call_name,
1158+
&signature,
1159+
&type_param_substitutions,
1160+
call_span,
1161+
);
1162+
check_message_channel_payload(
1163+
analyzer,
1164+
function,
1165+
&call_name,
1166+
&signature,
1167+
&type_param_substitutions,
1168+
call_span,
1169+
);
1170+
1171+
check_argument_types(
1172+
analyzer,
1173+
args,
1174+
&call_name,
1175+
signature,
1176+
&type_param_substitutions,
1177+
&resolved_names,
1178+
);
1179+
check_argument_escaping(
1180+
analyzer,
1181+
args,
1182+
&call_name,
1183+
signature,
1184+
noescape_bindings,
1185+
local_closure_bindings,
1186+
&resolved_names,
1187+
);
1188+
}
1189+
1190+
/// Phase 1: enforce argument naming rules — unnamed arguments where they are not
1191+
/// allowed, duplicate arguments, and unknown argument names.
1192+
#[allow(clippy::too_many_arguments)]
1193+
fn check_argument_naming(
1194+
analyzer: &mut Analyzer<'_>,
1195+
args: &[HirCallArg],
1196+
call_name: &str,
1197+
allow_positional_args: bool,
1198+
allow_constructor_field_shorthand: bool,
1199+
param_names: &HashSet<String>,
1200+
signature_params: &[ParamSig],
1201+
resolved_names: &[Option<&str>],
1202+
) {
1203+
for arg in args {
1204+
if arg.name.is_none()
1205+
&& !allow_positional_args
1206+
&& constructor_field_shorthand_name(allow_constructor_field_shorthand, arg, param_names)
1207+
.is_none()
11141208
{
11151209
analyzer.diagnostics.push(
11161210
Diagnostic::error(
@@ -1131,15 +1225,8 @@ fn check_call_args(
11311225

11321226
let mut seen_names = HashSet::new();
11331227
let mut seen_positional_params = HashSet::new();
1134-
for (index, arg) in args.iter().enumerate() {
1135-
let Some(name) = resolved_arg_param_name(
1136-
arg,
1137-
index,
1138-
allow_constructor_field_shorthand,
1139-
allow_positional_args,
1140-
&param_names,
1141-
&signature_params,
1142-
) else {
1228+
for (arg, resolved) in args.iter().zip(resolved_names) {
1229+
let Some(name) = *resolved else {
11431230
continue;
11441231
};
11451232
if arg.name.is_none() && !seen_positional_params.insert(name) {
@@ -1174,28 +1261,25 @@ fn check_call_args(
11741261
))
11751262
.with_fix(
11761263
"rename_argument",
1177-
format!("Use one of: {}.", join_param_names(&signature_params)),
1264+
format!("Use one of: {}.", join_param_names(signature_params)),
11781265
"manual",
11791266
),
11801267
);
11811268
}
11821269
}
1270+
}
11831271

1184-
let provided_names: HashSet<&str> = args
1185-
.iter()
1186-
.enumerate()
1187-
.filter_map(|(index, arg)| {
1188-
resolved_arg_param_name(
1189-
arg,
1190-
index,
1191-
allow_constructor_field_shorthand,
1192-
allow_positional_args,
1193-
&param_names,
1194-
&signature_params,
1195-
)
1196-
})
1197-
.collect();
1198-
for param in &signature_params {
1272+
/// Phase 2: enforce argument completeness — every required parameter must be
1273+
/// provided.
1274+
fn check_argument_completeness(
1275+
analyzer: &mut Analyzer<'_>,
1276+
call_name: &str,
1277+
call_span: &Span,
1278+
signature_params: &[ParamSig],
1279+
resolved_names: &[Option<&str>],
1280+
) {
1281+
let provided_names: HashSet<&str> = resolved_names.iter().filter_map(|name| *name).collect();
1282+
for param in signature_params {
11991283
if !provided_names.contains(param.name.as_str()) {
12001284
analyzer.diagnostics.push(
12011285
Diagnostic::error(
@@ -1219,16 +1303,20 @@ fn check_call_args(
12191303
);
12201304
}
12211305
}
1306+
}
12221307

1223-
for (index, arg) in args.iter().enumerate() {
1224-
let Some(name) = resolved_arg_param_name(
1225-
arg,
1226-
index,
1227-
allow_constructor_field_shorthand,
1228-
allow_positional_args,
1229-
&param_names,
1230-
&signature_params,
1231-
) else {
1308+
/// Phase 3: enforce call-site data effects (`read`/`mut`/`take`) for parameters
1309+
/// that declare one.
1310+
fn check_argument_effects(
1311+
analyzer: &mut Analyzer<'_>,
1312+
args: &[HirCallArg],
1313+
call_name: &str,
1314+
allow_positional_args: bool,
1315+
param_effects: &HashMap<String, &'static str>,
1316+
resolved_names: &[Option<&str>],
1317+
) {
1318+
for (arg, resolved) in args.iter().zip(resolved_names) {
1319+
let Some(name) = *resolved else {
12321320
continue;
12331321
};
12341322
let Some(expected) = param_effects.get(name) else {
@@ -1258,46 +1346,30 @@ fn check_call_args(
12581346
);
12591347
}
12601348
}
1349+
}
12611350

1262-
let type_param_substitutions =
1263-
call_type_param_substitutions(analyzer, Some(function), callee, args, signature);
1264-
check_generic_call_bounds(
1265-
analyzer,
1266-
function,
1267-
callee,
1268-
&call_name,
1269-
&signature,
1270-
&type_param_substitutions,
1271-
call_span,
1272-
);
1273-
check_message_channel_payload(
1274-
analyzer,
1275-
function,
1276-
&call_name,
1277-
&signature,
1278-
&type_param_substitutions,
1279-
call_span,
1280-
);
1281-
1282-
for (index, arg) in args.iter().enumerate() {
1283-
let Some(name) = resolved_arg_param_name(
1284-
arg,
1285-
index,
1286-
allow_constructor_field_shorthand,
1287-
allow_positional_args,
1288-
&param_names,
1289-
&signature_params,
1290-
) else {
1351+
/// Phase 4: check each argument's type against the resolved signature parameter
1352+
/// (after type-parameter substitution).
1353+
fn check_argument_types(
1354+
analyzer: &mut Analyzer<'_>,
1355+
args: &[HirCallArg],
1356+
call_name: &str,
1357+
signature: &FunctionSig,
1358+
type_param_substitutions: &HashMap<String, String>,
1359+
resolved_names: &[Option<&str>],
1360+
) {
1361+
for (arg, resolved) in args.iter().zip(resolved_names) {
1362+
let Some(name) = *resolved else {
12911363
continue;
12921364
};
12931365
let Some(expected_param) = signature.params.iter().find(|param| param.name == name) else {
12941366
continue;
12951367
};
12961368
let expected_type =
1297-
substitute_type_params(&expected_param.type_name, &type_param_substitutions);
1369+
substitute_type_params(&expected_param.type_name, type_param_substitutions);
12981370
if check_fn_closure_contract(
12991371
analyzer,
1300-
&call_name,
1372+
call_name,
13011373
name,
13021374
&expected_type,
13031375
&signature.type_params,
@@ -1308,13 +1380,8 @@ fn check_call_args(
13081380
if type_contains_unresolved_generic(&expected_type, &signature.type_params) {
13091381
continue;
13101382
}
1311-
if check_argument_variant_payload_type(
1312-
analyzer,
1313-
&call_name,
1314-
name,
1315-
&expected_type,
1316-
&arg.value,
1317-
) {
1383+
if check_argument_variant_payload_type(analyzer, call_name, name, &expected_type, &arg.value)
1384+
{
13181385
continue;
13191386
}
13201387
let Some(actual_type) = hir_expr_type_name(&arg.value) else {
@@ -1352,17 +1419,22 @@ fn check_call_args(
13521419
));
13531420
}
13541421
}
1422+
}
13551423

1356-
for (index, arg) in args.iter().enumerate() {
1357-
let expected_param = resolved_arg_param_name(
1358-
arg,
1359-
index,
1360-
allow_constructor_field_shorthand,
1361-
allow_positional_args,
1362-
&param_names,
1363-
&signature_params,
1364-
)
1365-
.and_then(|name| signature.params.iter().find(|param| param.name == name));
1424+
/// Phase 5: enforce noescape and local-closure escape rules for each argument,
1425+
/// skipping parameters typed as `noescape` function types.
1426+
fn check_argument_escaping(
1427+
analyzer: &mut Analyzer<'_>,
1428+
args: &[HirCallArg],
1429+
call_name: &str,
1430+
signature: &FunctionSig,
1431+
noescape_bindings: &HashMap<String, CallbackBinding>,
1432+
local_closure_bindings: &HashMap<String, Span>,
1433+
resolved_names: &[Option<&str>],
1434+
) {
1435+
for (arg, resolved) in args.iter().zip(resolved_names) {
1436+
let expected_param =
1437+
resolved.and_then(|name| signature.params.iter().find(|param| param.name == name));
13661438
if expected_param.is_some_and(|param| is_noescape_fn_type(&param.type_name)) {
13671439
continue;
13681440
}
@@ -1371,14 +1443,14 @@ fn check_call_args(
13711443
&arg.value,
13721444
&arg.span,
13731445
noescape_bindings,
1374-
NoescapeEscapeContext::Pass { callee: &call_name },
1446+
NoescapeEscapeContext::Pass { callee: call_name },
13751447
);
13761448
check_local_closure_escape(
13771449
analyzer,
13781450
&arg.value,
13791451
&arg.span,
13801452
local_closure_bindings,
1381-
LocalClosureEscapeContext::Pass { callee: &call_name },
1453+
LocalClosureEscapeContext::Pass { callee: call_name },
13821454
);
13831455
}
13841456
}

0 commit comments

Comments
 (0)