compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts TYPESCRIPT 965 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import {9  CompilerDiagnostic,10  CompilerError,11  ErrorCategory,12} from '../CompilerError';13import {14  BlockId,15  GeneratedSource,16  HIRFunction,17  IdentifierId,18  Place,19  SourceLocation,20  getHookKindForType,21  isRefValueType,22  isUseRefType,23} from '../HIR';24import {25  eachInstructionOperand,26  eachInstructionValueOperand,27  eachPatternOperand,28  eachTerminalOperand,29} from '../HIR/visitors';30import {retainWhere} from '../Utils/utils';3132/**33 * Validates that a function does not access a ref value during render. This includes a partial check34 * for ref values which are accessed indirectly via function expressions.35 *36 * ```javascript37 * // ERROR38 * const ref = useRef();39 * ref.current;40 *41 * const ref = useRef();42 * foo(ref); // may access .current43 *44 * // ALLOWED45 * const ref = useHookThatReturnsRef();46 * ref.current;47 * ```48 *49 * In the future we may reject more cases, based on either object names (`fooRef.current` is likely a ref)50 * or based on property name alone (`foo.current` might be a ref).51 */5253const opaqueRefId = Symbol();54type RefId = number & {[opaqueRefId]: 'RefId'};5556function makeRefId(id: number): RefId {57  CompilerError.invariant(id >= 0 && Number.isInteger(id), {58    reason: 'Expected identifier id to be a non-negative integer',59    loc: GeneratedSource,60  });61  return id as RefId;62}63let _refId = 0;64function nextRefId(): RefId {65  return makeRefId(_refId++);66}6768type RefAccessType =69  | {kind: 'None'}70  | {kind: 'Nullable'}71  | {kind: 'Guard'; refId: RefId}72  | RefAccessRefType;7374type RefAccessRefType =75  | {kind: 'Ref'; refId: RefId}76  | {kind: 'RefValue'; loc?: SourceLocation; refId?: RefId}77  | {kind: 'Structure'; value: null | RefAccessRefType; fn: null | RefFnType};7879type RefFnType = {readRefEffect: boolean; returnType: RefAccessType};8081class Env {82  #changed = false;83  #data: Map<IdentifierId, RefAccessType> = new Map();84  #temporaries: Map<IdentifierId, Place> = new Map();8586  lookup(place: Place): Place {87    return this.#temporaries.get(place.identifier.id) ?? place;88  }8990  define(place: Place, value: Place): void {91    this.#temporaries.set(place.identifier.id, value);92  }9394  resetChanged(): void {95    this.#changed = false;96  }9798  hasChanged(): boolean {99    return this.#changed;100  }101102  get(key: IdentifierId): RefAccessType | undefined {103    const operandId = this.#temporaries.get(key)?.identifier.id ?? key;104    return this.#data.get(operandId);105  }106107  set(key: IdentifierId, value: RefAccessType): this {108    const operandId = this.#temporaries.get(key)?.identifier.id ?? key;109    const cur = this.#data.get(operandId);110    const widenedValue = joinRefAccessTypes(value, cur ?? {kind: 'None'});111    if (112      !(cur == null && widenedValue.kind === 'None') &&113      (cur == null || !tyEqual(cur, widenedValue))114    ) {115      this.#changed = true;116    }117    this.#data.set(operandId, widenedValue);118    return this;119  }120}121122export function validateNoRefAccessInRender(fn: HIRFunction): void {123  const env = new Env();124  collectTemporariesSidemap(fn, env);125  const errors = new CompilerError();126  validateNoRefAccessInRenderImpl(fn, env, errors);127  for (const detail of errors.details) {128    fn.env.recordError(detail);129  }130}131132function collectTemporariesSidemap(fn: HIRFunction, env: Env): void {133  for (const block of fn.body.blocks.values()) {134    for (const instr of block.instructions) {135      const {lvalue, value} = instr;136      switch (value.kind) {137        case 'LoadLocal': {138          const temp = env.lookup(value.place);139          if (temp != null) {140            env.define(lvalue, temp);141          }142          break;143        }144        case 'StoreLocal': {145          const temp = env.lookup(value.value);146          if (temp != null) {147            env.define(lvalue, temp);148            env.define(value.lvalue.place, temp);149          }150          break;151        }152        case 'PropertyLoad': {153          if (154            isUseRefType(value.object.identifier) &&155            value.property === 'current'156          ) {157            continue;158          }159          const temp = env.lookup(value.object);160          if (temp != null) {161            env.define(lvalue, temp);162          }163          break;164        }165      }166    }167  }168}169170function refTypeOfType(place: Place): RefAccessType {171  if (isRefValueType(place.identifier)) {172    return {kind: 'RefValue'};173  } else if (isUseRefType(place.identifier)) {174    return {kind: 'Ref', refId: nextRefId()};175  } else {176    return {kind: 'None'};177  }178}179180function tyEqual(a: RefAccessType, b: RefAccessType): boolean {181  if (a.kind !== b.kind) {182    return false;183  }184  switch (a.kind) {185    case 'None':186      return true;187    case 'Ref':188      return true;189    case 'Nullable':190      return true;191    case 'Guard':192      CompilerError.invariant(b.kind === 'Guard', {193        reason: 'Expected ref value',194        loc: GeneratedSource,195      });196      return a.refId === b.refId;197    case 'RefValue':198      CompilerError.invariant(b.kind === 'RefValue', {199        reason: 'Expected ref value',200        loc: GeneratedSource,201      });202      return a.loc == b.loc;203    case 'Structure': {204      CompilerError.invariant(b.kind === 'Structure', {205        reason: 'Expected structure',206        loc: GeneratedSource,207      });208      const fnTypesEqual =209        (a.fn === null && b.fn === null) ||210        (a.fn !== null &&211          b.fn !== null &&212          a.fn.readRefEffect === b.fn.readRefEffect &&213          tyEqual(a.fn.returnType, b.fn.returnType));214      return (215        fnTypesEqual &&216        (a.value === b.value ||217          (a.value !== null && b.value !== null && tyEqual(a.value, b.value)))218      );219    }220  }221}222223function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {224  function joinRefAccessRefTypes(225    a: RefAccessRefType,226    b: RefAccessRefType,227  ): RefAccessRefType {228    if (a.kind === 'RefValue') {229      if (b.kind === 'RefValue' && a.refId === b.refId) {230        return a;231      }232      return {kind: 'RefValue'};233    } else if (b.kind === 'RefValue') {234      return b;235    } else if (a.kind === 'Ref' || b.kind === 'Ref') {236      if (a.kind === 'Ref' && b.kind === 'Ref' && a.refId === b.refId) {237        return a;238      }239      return {kind: 'Ref', refId: nextRefId()};240    } else {241      CompilerError.invariant(242        a.kind === 'Structure' && b.kind === 'Structure',243        {244          reason: 'Expected structure',245          loc: GeneratedSource,246        },247      );248      const fn =249        a.fn === null250          ? b.fn251          : b.fn === null252            ? a.fn253            : {254                readRefEffect: a.fn.readRefEffect || b.fn.readRefEffect,255                returnType: joinRefAccessTypes(256                  a.fn.returnType,257                  b.fn.returnType,258                ),259              };260      const value =261        a.value === null262          ? b.value263          : b.value === null264            ? a.value265            : joinRefAccessRefTypes(a.value, b.value);266      return {267        kind: 'Structure',268        fn,269        value,270      };271    }272  }273274  return types.reduce(275    (a, b) => {276      if (a.kind === 'None') {277        return b;278      } else if (b.kind === 'None') {279        return a;280      } else if (a.kind === 'Guard') {281        if (b.kind === 'Guard' && a.refId === b.refId) {282          return a;283        } else if (b.kind === 'Nullable' || b.kind === 'Guard') {284          return {kind: 'None'};285        } else {286          return b;287        }288      } else if (b.kind === 'Guard') {289        if (a.kind === 'Nullable') {290          return {kind: 'None'};291        } else {292          return b;293        }294      } else if (a.kind === 'Nullable') {295        return b;296      } else if (b.kind === 'Nullable') {297        return a;298      } else {299        return joinRefAccessRefTypes(a, b);300      }301    },302    {kind: 'None'},303  );304}305306function validateNoRefAccessInRenderImpl(307  fn: HIRFunction,308  env: Env,309  errors: CompilerError,310): RefAccessType {311  let returnValues: Array<undefined | RefAccessType> = [];312  let place;313  for (const param of fn.params) {314    if (param.kind === 'Identifier') {315      place = param;316    } else {317      place = param.place;318    }319    const type = refTypeOfType(place);320    env.set(place.identifier.id, type);321  }322323  const interpolatedAsJsx = new Set<IdentifierId>();324  for (const block of fn.body.blocks.values()) {325    for (const instr of block.instructions) {326      const {value} = instr;327      if (value.kind === 'JsxExpression' || value.kind === 'JsxFragment') {328        if (value.children != null) {329          for (const child of value.children) {330            interpolatedAsJsx.add(child.identifier.id);331          }332        }333      }334    }335  }336337  for (let i = 0; (i == 0 || env.hasChanged()) && i < 10; i++) {338    env.resetChanged();339    returnValues = [];340    const safeBlocks: Array<{block: BlockId; ref: RefId}> = [];341    for (const [, block] of fn.body.blocks) {342      retainWhere(safeBlocks, entry => entry.block !== block.id);343      for (const phi of block.phis) {344        env.set(345          phi.place.identifier.id,346          joinRefAccessTypes(347            ...Array(...phi.operands.values()).map(348              operand =>349                env.get(operand.identifier.id) ?? ({kind: 'None'} as const),350            ),351          ),352        );353      }354355      for (const instr of block.instructions) {356        switch (instr.value.kind) {357          case 'JsxExpression':358          case 'JsxFragment': {359            for (const operand of eachInstructionValueOperand(instr.value)) {360              validateNoDirectRefValueAccess(errors, operand, env);361            }362            break;363          }364          case 'ComputedLoad':365          case 'PropertyLoad': {366            if (instr.value.kind === 'ComputedLoad') {367              validateNoDirectRefValueAccess(errors, instr.value.property, env);368            }369            const objType = env.get(instr.value.object.identifier.id);370            let lookupType: null | RefAccessType = null;371            if (objType?.kind === 'Structure') {372              lookupType = objType.value;373            } else if (objType?.kind === 'Ref') {374              lookupType = {375                kind: 'RefValue',376                loc: instr.loc,377                refId: objType.refId,378              };379            }380            env.set(381              instr.lvalue.identifier.id,382              lookupType ?? refTypeOfType(instr.lvalue),383            );384            break;385          }386          case 'TypeCastExpression': {387            env.set(388              instr.lvalue.identifier.id,389              env.get(instr.value.value.identifier.id) ??390                refTypeOfType(instr.lvalue),391            );392            break;393          }394          case 'LoadContext':395          case 'LoadLocal': {396            env.set(397              instr.lvalue.identifier.id,398              env.get(instr.value.place.identifier.id) ??399                refTypeOfType(instr.lvalue),400            );401            break;402          }403          case 'StoreContext':404          case 'StoreLocal': {405            env.set(406              instr.value.lvalue.place.identifier.id,407              env.get(instr.value.value.identifier.id) ??408                refTypeOfType(instr.value.lvalue.place),409            );410            env.set(411              instr.lvalue.identifier.id,412              env.get(instr.value.value.identifier.id) ??413                refTypeOfType(instr.lvalue),414            );415            break;416          }417          case 'Destructure': {418            const objType = env.get(instr.value.value.identifier.id);419            let lookupType = null;420            if (objType?.kind === 'Structure') {421              lookupType = objType.value;422            }423            env.set(424              instr.lvalue.identifier.id,425              lookupType ?? refTypeOfType(instr.lvalue),426            );427            for (const lval of eachPatternOperand(instr.value.lvalue.pattern)) {428              env.set(lval.identifier.id, lookupType ?? refTypeOfType(lval));429            }430            break;431          }432          case 'ObjectMethod':433          case 'FunctionExpression': {434            let returnType: RefAccessType = {kind: 'None'};435            let readRefEffect = false;436            const innerErrors = new CompilerError();437            const result = validateNoRefAccessInRenderImpl(438              instr.value.loweredFunc.func,439              env,440              innerErrors,441            );442            if (!innerErrors.hasAnyErrors()) {443              returnType = result;444            } else {445              readRefEffect = true;446            }447            env.set(instr.lvalue.identifier.id, {448              kind: 'Structure',449              fn: {450                readRefEffect,451                returnType,452              },453              value: null,454            });455            break;456          }457          case 'MethodCall':458          case 'CallExpression': {459            const callee =460              instr.value.kind === 'CallExpression'461                ? instr.value.callee462                : instr.value.property;463            const hookKind = getHookKindForType(fn.env, callee.identifier.type);464            let returnType: RefAccessType = {kind: 'None'};465            const fnType = env.get(callee.identifier.id);466            let didError = false;467            if (fnType?.kind === 'Structure' && fnType.fn !== null) {468              returnType = fnType.fn.returnType;469              if (fnType.fn.readRefEffect) {470                didError = true;471                errors.pushDiagnostic(472                  CompilerDiagnostic.create({473                    category: ErrorCategory.Refs,474                    reason: 'Cannot access refs during render',475                    description: ERROR_DESCRIPTION,476                  }).withDetails({477                    kind: 'error',478                    loc: callee.loc,479                    message: `This function accesses a ref value`,480                  }),481                );482              }483            }484            /*485             * If we already reported an error on this instruction, don't report486             * duplicate errors487             */488            if (!didError) {489              const isRefLValue = isUseRefType(instr.lvalue.identifier);490              if (491                isRefLValue ||492                (hookKind != null &&493                  hookKind !== 'useState' &&494                  hookKind !== 'useReducer')495              ) {496                for (const operand of eachInstructionValueOperand(497                  instr.value,498                )) {499                  /**500                   * Allow passing refs or ref-accessing functions when:501                   * 1. lvalue is a ref (mergeRefs pattern: `mergeRefs(ref1, ref2)`)502                   * 2. calling hooks (independently validated for ref safety)503                   */504                  validateNoDirectRefValueAccess(errors, operand, env);505                }506              } else if (interpolatedAsJsx.has(instr.lvalue.identifier.id)) {507                for (const operand of eachInstructionValueOperand(508                  instr.value,509                )) {510                  /**511                   * Special case: the lvalue is passed as a jsx child512                   *513                   * For example `<Foo>{renderHelper(ref)}</Foo>`. Here we have more514                   * context and infer that the ref is being passed to a component-like515                   * render function which attempts to obey the rules.516                   */517                  validateNoRefValueAccess(errors, env, operand);518                }519              } else if (hookKind == null && instr.effects != null) {520                /**521                 * For non-hook functions with known aliasing effects, use the522                 * effects to determine what validation to apply for each place.523                 * Track visited id:kind pairs to avoid duplicate errors.524                 */525                const visitedEffects: Set<string> = new Set();526                for (const effect of instr.effects) {527                  let place: Place | null = null;528                  let validation: 'ref-passed' | 'direct-ref' | 'none' = 'none';529                  switch (effect.kind) {530                    case 'Freeze': {531                      place = effect.value;532                      validation = 'direct-ref';533                      break;534                    }535                    case 'Mutate':536                    case 'MutateTransitive':537                    case 'MutateConditionally':538                    case 'MutateTransitiveConditionally': {539                      place = effect.value;540                      validation = 'ref-passed';541                      break;542                    }543                    case 'Render': {544                      place = effect.place;545                      validation = 'ref-passed';546                      break;547                    }548                    case 'Capture':549                    case 'Alias':550                    case 'MaybeAlias':551                    case 'Assign':552                    case 'CreateFrom': {553                      place = effect.from;554                      validation = 'ref-passed';555                      break;556                    }557                    case 'ImmutableCapture': {558                      /**559                       * ImmutableCapture can come from two sources:560                       * 1. A known signature that explicitly freezes the operand561                       *    (e.g. PanResponder.create)  safe, the function doesn't562                       *    call callbacks during render.563                       * 2. Downgraded defaults when the operand is already frozen564                       *    (e.g. foo(propRef))  the function is unknown and may565                       *    access the ref.566                       *567                       * We distinguish these by checking whether the same operand568                       * also has a Freeze effect on this instruction, which only569                       * comes from known signatures.570                       */571                      place = effect.from;572                      const isFrozen = instr.effects.some(573                        e =>574                          e.kind === 'Freeze' &&575                          e.value.identifier.id === effect.from.identifier.id,576                      );577                      validation = isFrozen ? 'direct-ref' : 'ref-passed';578                      break;579                    }580                    case 'Create':581                    case 'CreateFunction':582                    case 'Apply':583                    case 'Impure':584                    case 'MutateFrozen':585                    case 'MutateGlobal': {586                      break;587                    }588                  }589                  if (place !== null && validation !== 'none') {590                    const key = `${place.identifier.id}:${validation}`;591                    if (!visitedEffects.has(key)) {592                      visitedEffects.add(key);593                      if (validation === 'direct-ref') {594                        validateNoDirectRefValueAccess(errors, place, env);595                      } else {596                        validateNoRefPassedToFunction(597                          errors,598                          env,599                          place,600                          place.loc,601                        );602                      }603                    }604                  }605                }606              } else {607                for (const operand of eachInstructionValueOperand(608                  instr.value,609                )) {610                  validateNoRefPassedToFunction(611                    errors,612                    env,613                    operand,614                    operand.loc,615                  );616                }617              }618            }619            env.set(instr.lvalue.identifier.id, returnType);620            break;621          }622          case 'ObjectExpression':623          case 'ArrayExpression': {624            const types: Array<RefAccessType> = [];625            for (const operand of eachInstructionValueOperand(instr.value)) {626              validateNoDirectRefValueAccess(errors, operand, env);627              types.push(env.get(operand.identifier.id) ?? {kind: 'None'});628            }629            const value = joinRefAccessTypes(...types);630            if (631              value.kind === 'None' ||632              value.kind === 'Guard' ||633              value.kind === 'Nullable'634            ) {635              env.set(instr.lvalue.identifier.id, {kind: 'None'});636            } else {637              env.set(instr.lvalue.identifier.id, {638                kind: 'Structure',639                value,640                fn: null,641              });642            }643            break;644          }645          case 'PropertyDelete':646          case 'PropertyStore':647          case 'ComputedDelete':648          case 'ComputedStore': {649            const target = env.get(instr.value.object.identifier.id);650            let safe: (typeof safeBlocks)['0'] | null | undefined = null;651            if (652              instr.value.kind === 'PropertyStore' &&653              target != null &&654              target.kind === 'Ref'655            ) {656              safe = safeBlocks.find(entry => entry.ref === target.refId);657            }658            if (safe != null) {659              retainWhere(safeBlocks, entry => entry !== safe);660            } else {661              validateNoRefUpdate(errors, env, instr.value.object, instr.loc);662            }663            if (664              instr.value.kind === 'ComputedDelete' ||665              instr.value.kind === 'ComputedStore'666            ) {667              validateNoRefValueAccess(errors, env, instr.value.property);668            }669            if (670              instr.value.kind === 'ComputedStore' ||671              instr.value.kind === 'PropertyStore'672            ) {673              validateNoDirectRefValueAccess(errors, instr.value.value, env);674              const type = env.get(instr.value.value.identifier.id);675              if (type != null && type.kind === 'Structure') {676                let objectType: RefAccessType = type;677                if (target != null) {678                  objectType = joinRefAccessTypes(objectType, target);679                }680                env.set(instr.value.object.identifier.id, objectType);681              }682            }683            break;684          }685          case 'StartMemoize':686          case 'FinishMemoize':687            break;688          case 'LoadGlobal': {689            if (instr.value.binding.name === 'undefined') {690              env.set(instr.lvalue.identifier.id, {kind: 'Nullable'});691            }692            break;693          }694          case 'Primitive': {695            if (instr.value.value == null) {696              env.set(instr.lvalue.identifier.id, {kind: 'Nullable'});697            }698            break;699          }700          case 'UnaryExpression': {701            if (instr.value.operator === '!') {702              const value = env.get(instr.value.value.identifier.id);703              const refId =704                value?.kind === 'RefValue' && value.refId != null705                  ? value.refId706                  : null;707              if (refId !== null) {708                /*709                 * Record an error suggesting the `if (ref.current == null)` pattern,710                 * but also record the lvalue as a guard so that we don't emit a second711                 * error for the write to the ref712                 */713                env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});714                errors.pushDiagnostic(715                  CompilerDiagnostic.create({716                    category: ErrorCategory.Refs,717                    reason: 'Cannot access refs during render',718                    description: ERROR_DESCRIPTION,719                  })720                    .withDetails({721                      kind: 'error',722                      loc: instr.value.value.loc,723                      message: `Cannot access ref value during render`,724                    })725                    .withDetails({726                      kind: 'hint',727                      message:728                        'To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`',729                    }),730                );731                break;732              }733            }734            validateNoRefValueAccess(errors, env, instr.value.value);735            break;736          }737          case 'BinaryExpression': {738            const left = env.get(instr.value.left.identifier.id);739            const right = env.get(instr.value.right.identifier.id);740            let nullish: boolean = false;741            let refId: RefId | null = null;742            if (left?.kind === 'RefValue' && left.refId != null) {743              refId = left.refId;744            } else if (right?.kind === 'RefValue' && right.refId != null) {745              refId = right.refId;746            }747748            if (left?.kind === 'Nullable') {749              nullish = true;750            } else if (right?.kind === 'Nullable') {751              nullish = true;752            }753754            if (refId !== null && nullish) {755              env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});756            } else {757              for (const operand of eachInstructionValueOperand(instr.value)) {758                validateNoRefValueAccess(errors, env, operand);759              }760            }761            break;762          }763          default: {764            for (const operand of eachInstructionValueOperand(instr.value)) {765              validateNoRefValueAccess(errors, env, operand);766            }767            break;768          }769        }770771        // Guard values are derived from ref.current, so they can only be used in if statement targets772        for (const operand of eachInstructionOperand(instr)) {773          guardCheck(errors, operand, env);774        }775776        if (777          isUseRefType(instr.lvalue.identifier) &&778          env.get(instr.lvalue.identifier.id)?.kind !== 'Ref'779        ) {780          env.set(781            instr.lvalue.identifier.id,782            joinRefAccessTypes(783              env.get(instr.lvalue.identifier.id) ?? {kind: 'None'},784              {kind: 'Ref', refId: nextRefId()},785            ),786          );787        }788        if (789          isRefValueType(instr.lvalue.identifier) &&790          env.get(instr.lvalue.identifier.id)?.kind !== 'RefValue'791        ) {792          env.set(793            instr.lvalue.identifier.id,794            joinRefAccessTypes(795              env.get(instr.lvalue.identifier.id) ?? {kind: 'None'},796              {kind: 'RefValue', loc: instr.loc},797            ),798          );799        }800      }801802      if (block.terminal.kind === 'if') {803        const test = env.get(block.terminal.test.identifier.id);804        if (805          test?.kind === 'Guard' &&806          safeBlocks.find(entry => entry.ref === test.refId) == null807        ) {808          safeBlocks.push({block: block.terminal.fallthrough, ref: test.refId});809        }810      }811812      for (const operand of eachTerminalOperand(block.terminal)) {813        if (block.terminal.kind !== 'return') {814          validateNoRefValueAccess(errors, env, operand);815          if (block.terminal.kind !== 'if') {816            guardCheck(errors, operand, env);817          }818        } else {819          // Allow functions containing refs to be returned, but not direct ref values820          validateNoDirectRefValueAccess(errors, operand, env);821          guardCheck(errors, operand, env);822          returnValues.push(env.get(operand.identifier.id));823        }824      }825    }826827    if (errors.hasAnyErrors()) {828      return {kind: 'None'};829    }830  }831832  CompilerError.invariant(!env.hasChanged(), {833    reason: 'Ref type environment did not converge',834    loc: GeneratedSource,835  });836837  return joinRefAccessTypes(838    ...returnValues.filter((env): env is RefAccessType => env !== undefined),839  );840}841842function destructure(843  type: RefAccessType | undefined,844): RefAccessType | undefined {845  if (type?.kind === 'Structure' && type.value !== null) {846    return destructure(type.value);847  }848  return type;849}850851function guardCheck(errors: CompilerError, operand: Place, env: Env): void {852  if (env.get(operand.identifier.id)?.kind === 'Guard') {853    errors.pushDiagnostic(854      CompilerDiagnostic.create({855        category: ErrorCategory.Refs,856        reason: 'Cannot access refs during render',857        description: ERROR_DESCRIPTION,858      }).withDetails({859        kind: 'error',860        loc: operand.loc,861        message: `Cannot access ref value during render`,862      }),863    );864  }865}866867function validateNoRefValueAccess(868  errors: CompilerError,869  env: Env,870  operand: Place,871): void {872  const type = destructure(env.get(operand.identifier.id));873  if (874    type?.kind === 'RefValue' ||875    (type?.kind === 'Structure' && type.fn?.readRefEffect)876  ) {877    errors.pushDiagnostic(878      CompilerDiagnostic.create({879        category: ErrorCategory.Refs,880        reason: 'Cannot access refs during render',881        description: ERROR_DESCRIPTION,882      }).withDetails({883        kind: 'error',884        loc: (type.kind === 'RefValue' && type.loc) || operand.loc,885        message: `Cannot access ref value during render`,886      }),887    );888  }889}890891function validateNoRefPassedToFunction(892  errors: CompilerError,893  env: Env,894  operand: Place,895  loc: SourceLocation,896): void {897  const type = destructure(env.get(operand.identifier.id));898  if (899    type?.kind === 'Ref' ||900    type?.kind === 'RefValue' ||901    (type?.kind === 'Structure' && type.fn?.readRefEffect)902  ) {903    errors.pushDiagnostic(904      CompilerDiagnostic.create({905        category: ErrorCategory.Refs,906        reason: 'Cannot access refs during render',907        description: ERROR_DESCRIPTION,908      }).withDetails({909        kind: 'error',910        loc: (type.kind === 'RefValue' && type.loc) || loc,911        message: `Passing a ref to a function may read its value during render`,912      }),913    );914  }915}916917function validateNoRefUpdate(918  errors: CompilerError,919  env: Env,920  operand: Place,921  loc: SourceLocation,922): void {923  const type = destructure(env.get(operand.identifier.id));924  if (type?.kind === 'Ref' || type?.kind === 'RefValue') {925    errors.pushDiagnostic(926      CompilerDiagnostic.create({927        category: ErrorCategory.Refs,928        reason: 'Cannot access refs during render',929        description: ERROR_DESCRIPTION,930      }).withDetails({931        kind: 'error',932        loc: (type.kind === 'RefValue' && type.loc) || loc,933        message: `Cannot update ref during render`,934      }),935    );936  }937}938939function validateNoDirectRefValueAccess(940  errors: CompilerError,941  operand: Place,942  env: Env,943): void {944  const type = destructure(env.get(operand.identifier.id));945  if (type?.kind === 'RefValue') {946    errors.pushDiagnostic(947      CompilerDiagnostic.create({948        category: ErrorCategory.Refs,949        reason: 'Cannot access refs during render',950        description: ERROR_DESCRIPTION,951      }).withDetails({952        kind: 'error',953        loc: type.loc ?? operand.loc,954        message: `Cannot access ref value during render`,955      }),956    );957  }958}959960const ERROR_DESCRIPTION =961  'React refs are values that are not needed for rendering. Refs should only be accessed ' +962  'outside of render, such as in event handlers or effects. ' +963  'Accessing a ref value (the `current` property) during render can cause your component ' +964  'not to update as expected (https://react.dev/reference/react/useRef)';

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.