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.