[hir] infer reference effects for property call
--- This PR does not add inference for normal `CallExpression`s, since built-in functions for `Array` and `Object` are usually only valid if called with a correctly-typed `this`. If we want codegen to preserve source code semantics, Forget should only add inferred types it is confident about. This PR also adds `returnEffect` to FunctionSignature. `returnEffect = Store` if this function is known to always return a captured value from `receiver` or `args`.
Mofei Zhang committed
Mar 23, 2023 at 15:08 UTC
c3589a9565a766389410072ca2737f42d407fbb6
1 file changed
+38
-50
compiler/forget/src/Inference/InferReferenceEffects.ts
+38
-50
@@ -695,29 +695,31 @@ function inferBlock(
695
env,
696
instrValue.property.identifier.type
697
);
698
- if (signature !== null) {
699
- const effects = getFunctionCallEffects(
700
- instrValue,
701
- signature,
702
- Effect.Mutate
703
- );
704
- for (const [place, effect] of effects) {
705
- state.referenceAndCheckError(place, effect);
698
+
699
+ const effects =
700
+ signature !== null
701
+ ? getMethodCallEffects(instrValue, signature)
702
+ : null;
703
+ for (let i = 0; i < instrValue.args.length; i++) {
704
+ const arg = instrValue.args[i];
705
+ const place = arg.kind === "Identifier" ? arg : arg.place;
706
+ if (effects !== null) {
707
+ // If effects are inferred for an argument, we should fail invalid
708
+ // mutating effects
709
+ state.referenceAndCheckError(place, effects[i]);
710
+ } else {
711
+ state.reference(place, Effect.Mutate);
712
}
713
+ }
714
+ if (signature !== null) {
715
state.referenceAndCheckError(
716
instrValue.receiver,
717
signature.calleeEffect
718
);
719
} else {
712
- for (const arg of instrValue.args) {
713
- if (arg.kind === "Identifier") {
714
- state.reference(arg, Effect.Mutate);
715
- } else {
716
- state.reference(arg.place, Effect.Mutate);
717
- }
718
- }
720
state.reference(instrValue.receiver, Effect.Mutate);
721
}
722
+
723
state.initialize(instrValue, ValueKind.Mutable);
724
state.define(instr.lvalue, instrValue);
725
instr.lvalue.effect = Effect.Mutate;
@@ -906,49 +908,35 @@ function getFunctionCallSignature(
908
}
909
910
/**
909
- * Make a best attempt at matching arguments of a PropertyCall to its FunctionSignature,
910
- * calling back to `defaultEffect` when we are unable to.
911
+ * Make a best attempt at matching arguments of a {@link MethodCall} to parameter effects.
912
+ * defined in its {@link FunctionSignature}.
913
*
914
* @param fn
915
* @param sig
914
- * @param defaultEffect In the case that inference fails, all arguments will be inferred
915
- * as defaultEffect
916
- * @returns Inferred effects of function arguments
916
+ * @returns Inferred effects of function arguments, or null if inference fails.
917
*/
918
-function getFunctionCallEffects(
918
+function getMethodCallEffects(
919
fn: MethodCall,
920
- sig: FunctionSignature,
921
- defaultEffect: Effect
922
-): Array<[Place, Effect]> {
923
- const inferredEffects: Array<[Place, Effect | null]> = fn.args.map(
924
- (arg, idx) => {
925
- const argPlace = arg.kind === "Identifier" ? arg : arg.place;
926
- if (idx < sig.positionalParams.length) {
927
- // Only infer effects when there is a direct mapping positional arg --> positional param
928
- // Otherwise, return null to indicate inference failed
929
- if (arg.kind === "Identifier") {
930
- return [argPlace, sig.positionalParams[idx]];
931
- } else {
932
- return [argPlace, null];
933
- }
934
- } else if (sig.restParam !== null) {
935
- return [argPlace, sig.restParam];
920
+ sig: FunctionSignature
921
+): Array<Effect> | null {
922
+ const results = [];
923
+ for (let i = 0; i < fn.args.length; i++) {
924
+ const arg = fn.args[i];
925
+ if (i < sig.positionalParams.length) {
926
+ // Only infer effects when there is a direct mapping positional arg --> positional param
927
+ // Otherwise, return null to indicate inference failed
928
+ if (arg.kind === "Identifier") {
929
+ results.push(sig.positionalParams[i]);
930
} else {
937
- // If there are more arguments than positional arguments, we'll also assume
938
- // that inference failed
939
- return [argPlace, null];
931
+ return null;
932
}
933
+ } else if (sig.restParam !== null) {
934
+ results.push(sig.restParam);
935
+ } else {
936
+ // If there are more arguments than positional arguments and a rest parameter is not
937
+ // defined, we'll also assume that inference failed
938
+ return null;
939
}
942
- );
943
-
944
- let results: Array<[Place, Effect]>;
945
- if (inferredEffects.some(([_, effect]) => effect === null)) {
946
- // If inference failed for any argument, give up on inference for all arguments
947
- results = inferredEffects.map(([arg, _]) => {
948
- return [arg, defaultEffect];
949
- });
950
- } else {
951
- results = inferredEffects as Array<[Place, Effect]>;
940
}
941
return results;
942
}