38
import {
39
eachInstructionValueLValue,
40
eachInstructionValueOperand,
41
+ eachTerminalOperand,
42
eachTerminalSuccessor,
43
} from '../HIR/visitors';
44
import {Ok, Result} from '../Utils/Result';
222
return Ok(undefined);
223
}
224
224
-function findHoistedContextDeclarations(fn: HIRFunction): Set<DeclarationId> {
225
- const hoisted = new Set<DeclarationId>();
225
+function findHoistedContextDeclarations(
226
+ fn: HIRFunction,
227
+): Map<DeclarationId, Place | null> {
228
+ const hoisted = new Map<DeclarationId, Place | null>();
229
+ function visit(place: Place): void {
230
+ if (
231
+ hoisted.has(place.identifier.declarationId) &&
232
+ hoisted.get(place.identifier.declarationId) == null
233
+ ) {
234
+ // If this is the first load of the value, store the location
235
+ hoisted.set(place.identifier.declarationId, place);
236
+ }
237
+ }
238
for (const block of fn.body.blocks.values()) {
239
for (const instr of block.instructions) {
240
if (instr.value.kind === 'DeclareContext') {
244
kind == InstructionKind.HoistedFunction ||
245
kind == InstructionKind.HoistedLet
246
) {
235
- hoisted.add(instr.value.lvalue.place.identifier.declarationId);
247
+ hoisted.set(instr.value.lvalue.place.identifier.declarationId, null);
248
+ }
249
+ } else {
250
+ for (const operand of eachInstructionValueOperand(instr.value)) {
251
+ visit(operand);
252
}
253
}
254
}
255
+ for (const operand of eachTerminalOperand(block.terminal)) {
256
+ visit(operand);
257
+ }
258
}
259
return hoisted;
260
}
267
catchHandlers: Map<BlockId, Place> = new Map();
268
isFuctionExpression: boolean;
269
fn: HIRFunction;
251
- hoistedContextDeclarations: Set<DeclarationId>;
270
+ hoistedContextDeclarations: Map<DeclarationId, Place | null>;
271
272
constructor(
273
isFunctionExpression: boolean,
274
fn: HIRFunction,
256
- hoistedContextDeclarations: Set<DeclarationId>,
275
+ hoistedContextDeclarations: Map<DeclarationId, Place | null>,
276
) {
277
this.isFuctionExpression = isFunctionExpression;
278
this.fn = fn;
920
console.log(prettyFormat(state.debugAbstractValue(value)));
921
}
922
904
- let reason: string;
905
- let description: string | null = null;
906
-
923
if (
924
mutationKind === 'mutate-frozen' &&
925
context.hoistedContextDeclarations.has(
926
effect.value.identifier.declarationId,
927
)
928
) {
913
- reason = `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`;
914
- if (
929
+ const description =
930
effect.value.identifier.name !== null &&
931
effect.value.identifier.name.kind === 'named'
917
- ) {
918
- description = `Move the declaration of \`${effect.value.identifier.name.value}\` to before it is first referenced`;
932
+ ? `Variable \`${effect.value.identifier.name.value}\` is accessed before it is declared`
933
+ : null;
934
+ const hoistedAccess = context.hoistedContextDeclarations.get(
935
+ effect.value.identifier.declarationId,
936
+ );
937
+ if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
938
+ effects.push({
939
+ kind: 'MutateFrozen',
940
+ place: effect.value,
941
+ error: {
942
+ severity: ErrorSeverity.InvalidReact,
943
+ reason: `This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time`,
944
+ description,
945
+ loc: hoistedAccess.loc,
946
+ suggestions: null,
947
+ },
948
+ });
949
}
950
+
951
+ effects.push({
952
+ kind: 'MutateFrozen',
953
+ place: effect.value,
954
+ error: {
955
+ severity: ErrorSeverity.InvalidReact,
956
+ reason: `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
957
+ description,
958
+ loc: effect.value.loc,
959
+ suggestions: null,
960
+ },
961
+ });
962
} else {
921
- reason = getWriteErrorReason({
963
+ const reason = getWriteErrorReason({
964
kind: value.kind,
965
reason: value.reason,
966
context: new Set(),
967
});
926
- if (
968
+ const description =
969
effect.value.identifier.name !== null &&
970
effect.value.identifier.name.kind === 'named'
929
- ) {
930
- description = `Found mutation of \`${effect.value.identifier.name.value}\``;
931
- }
971
+ ? `Found mutation of \`${effect.value.identifier.name.value}\``
972
+ : null;
973
+ effects.push({
974
+ kind:
975
+ value.kind === ValueKind.Frozen ? 'MutateFrozen' : 'MutateGlobal',
976
+ place: effect.value,
977
+ error: {
978
+ severity: ErrorSeverity.InvalidReact,
979
+ reason,
980
+ description,
981
+ loc: effect.value.loc,
982
+ suggestions: null,
983
+ },
984
+ });
985
}
933
-
934
- effects.push({
935
- kind:
936
- value.kind === ValueKind.Frozen ? 'MutateFrozen' : 'MutateGlobal',
937
- place: effect.value,
938
- error: {
939
- severity: ErrorSeverity.InvalidReact,
940
- reason,
941
- description,
942
- loc: effect.value.loc,
943
- suggestions: null,
944
- },
945
- });
986
}
987
break;
988
}