@samitouri / QOS-React-2 / commits / c3589a9565

[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 }