Skip to content

Commit ebf320e

Browse files
fix(compiler): ensure that partially compiled queries can handle forward references
When a partially compiled component or directive is "linked" in JIT mode, the body of its declaration is evaluated by the JavaScript runtime. If a class is referenced in a query (e.g. `ViewQuery` or `ContentQuery`) but its definition is later in the file, then the reference must be wrapped in a `forwardRef()` call. Previously, query predicates were not wrapped correctly in partial declarations causing the code to crash at runtime. In AOT mode, this code is never evaluated but instead transformed as part of the build, so this bug did not become apparent until Angular Material started running JIT mode tests on its distributable output. This change fixes this problem by noting when queries are wrapped in `forwardRef()` calls and ensuring that this gets passed through to partial compilation declarations and then suitably stripped during linking. See angular/components#23882 and angular/components#23907
1 parent 0c0698a commit ebf320e

File tree

21 files changed

+461
-67
lines changed

21 files changed

+461
-67
lines changed

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig, makeBindingParser, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata} from '@angular/compiler';
8+
import {compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata} from '@angular/compiler';
99
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/compiler/src/core';
1010
import * as o from '@angular/compiler/src/output/output_ast';
1111

@@ -71,8 +71,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
7171
const type = directiveExpr.getValue('type');
7272
const selector = directiveExpr.getString('selector');
7373

74-
const {expression: typeExpr, isForwardRef} = extractForwardRef(type);
75-
if (isForwardRef) {
74+
const {expression: typeExpr, forwardRef} = extractForwardRef(type);
75+
if (forwardRef === ForwardRefHandling.Unwrapped) {
7676
declarationListEmitMode = DeclarationListEmitMode.Closure;
7777
}
7878

@@ -103,8 +103,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
103103
let pipes = new Map<string, o.Expression>();
104104
if (metaObj.has('pipes')) {
105105
pipes = metaObj.getObject('pipes').toMap(pipe => {
106-
const {expression: pipeType, isForwardRef} = extractForwardRef(pipe);
107-
if (isForwardRef) {
106+
const {expression: pipeType, forwardRef} = extractForwardRef(pipe);
107+
if (forwardRef === ForwardRefHandling.Unwrapped) {
108108
declarationListEmitMode = DeclarationListEmitMode.Closure;
109109
}
110110
return pipeType;

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {AstObject, AstValue} from '../../ast/ast_value';
1414
import {FatalLinkerError} from '../../fatal_linker_error';
1515

1616
import {PartialLinker} from './partial_linker';
17-
import {wrapReference} from './util';
17+
import {extractForwardRef, wrapReference} from './util';
1818

1919
/**
2020
* A `PartialLinker` that is designed to process `ɵɵngDeclareDirective()` call expressions.
@@ -141,7 +141,7 @@ function toQueryMetadata<TExpression>(obj: AstObject<R3DeclareQueryMetadata, TEx
141141
if (predicateExpr.isArray()) {
142142
predicate = predicateExpr.getArray().map(entry => entry.getString());
143143
} else {
144-
predicate = predicateExpr.getOpaque();
144+
predicate = extractForwardRef(predicateExpr);
145145
}
146146
return {
147147
propertyName: obj.getString('propertyName'),

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_injectable_linker_1.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {compileInjectable, ConstantPool, createMayBeForwardRefExpression, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
8+
import {compileInjectable, ConstantPool, createMayBeForwardRefExpression, ForwardRefHandling, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
99
import * as o from '@angular/compiler/src/output/output_ast';
1010

1111
import {AstObject} from '../../ast/ast_value';
@@ -44,8 +44,9 @@ export function toR3InjectableMeta<TExpression>(
4444
type: wrapReference(typeExpr.getOpaque()),
4545
internalType: typeExpr.getOpaque(),
4646
typeArgumentCount: 0,
47-
providedIn: metaObj.has('providedIn') ? extractForwardRef(metaObj.getValue('providedIn')) :
48-
createMayBeForwardRefExpression(o.literal(null), false),
47+
providedIn: metaObj.has('providedIn') ?
48+
extractForwardRef(metaObj.getValue('providedIn')) :
49+
createMayBeForwardRefExpression(o.literal(null), ForwardRefHandling.None),
4950
};
5051

5152
if (metaObj.has('useClass')) {

packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {createMayBeForwardRefExpression, MaybeForwardRefExpression, R3DeclareDependencyMetadata, R3DependencyMetadata, R3Reference} from '@angular/compiler';
8+
import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpression, R3DeclareDependencyMetadata, R3DependencyMetadata, R3Reference} from '@angular/compiler';
99
import * as o from '@angular/compiler/src/output/output_ast';
1010

1111
import {AstObject, AstValue} from '../../ast/ast_value';
@@ -69,7 +69,7 @@ export function getDependency<TExpression>(
6969
export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
7070
MaybeForwardRefExpression<o.WrappedNodeExpr<TExpression>> {
7171
if (!expr.isCallExpression()) {
72-
return createMayBeForwardRefExpression(expr.getOpaque(), /* isForwardRef */ false);
72+
return createMayBeForwardRefExpression(expr.getOpaque(), ForwardRefHandling.None);
7373
}
7474

7575
const callee = expr.getCallee();
@@ -91,5 +91,6 @@ export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpressi
9191
wrapperFn, 'Unsupported `forwardRef(fn)` call, expected its argument to be a function');
9292
}
9393

94-
return createMayBeForwardRefExpression(wrapperFn.getFunctionReturnValue().getOpaque(), true);
94+
return createMayBeForwardRefExpression(
95+
wrapperFn.getFunctionReturnValue().getOpaque(), ForwardRefHandling.Unwrapped);
9596
}

packages/compiler-cli/src/ngtsc/annotations/src/directive.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
9+
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, createMayBeForwardRefExpression, Expression, ExternalExpr, FactoryTarget, ForwardRefHandling, getSafePropertyAccessString, makeBindingParser, MaybeForwardRefExpression, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
1010
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
1111
import * as ts from 'typescript';
1212

@@ -532,17 +532,21 @@ export function extractQueryMetadata(
532532
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
533533
}
534534
const first = name === 'ViewChild' || name === 'ContentChild';
535-
const node = tryUnwrapForwardRef(args[0], reflector) ?? args[0];
535+
const forwardReferenceTarget = tryUnwrapForwardRef(args[0], reflector);
536+
const node = forwardReferenceTarget ?? args[0];
537+
536538
const arg = evaluator.evaluate(node);
537539

538540
/** Whether or not this query should collect only static results (see view/api.ts) */
539541
let isStatic: boolean = false;
540542

541543
// Extract the predicate
542-
let predicate: Expression|string[]|null = null;
544+
let predicate: MaybeForwardRefExpression|string[]|null = null;
543545
if (arg instanceof Reference || arg instanceof DynamicValue) {
544546
// References and predicates that could not be evaluated statically are emitted as is.
545-
predicate = new WrappedNodeExpr(node);
547+
predicate = createMayBeForwardRefExpression(
548+
new WrappedNodeExpr(node),
549+
forwardReferenceTarget !== null ? ForwardRefHandling.Unwrapped : ForwardRefHandling.None);
546550
} else if (typeof arg === 'string') {
547551
predicate = [arg];
548552
} else if (isStringArrayOrDie(arg, `@${name} predicate`, node)) {

packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createMayBeForwardRefExpression, FactoryTarget, LiteralExpr, MaybeForwardRefExpression, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr} from '@angular/compiler';
9+
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createMayBeForwardRefExpression, FactoryTarget, ForwardRefHandling, LiteralExpr, MaybeForwardRefExpression, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, WrappedNodeExpr} from '@angular/compiler';
1010
import * as ts from 'typescript';
1111

1212
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
@@ -162,7 +162,7 @@ function extractInjectableMetadata(
162162
type,
163163
typeArgumentCount,
164164
internalType,
165-
providedIn: createMayBeForwardRefExpression(new LiteralExpr(null), false),
165+
providedIn: createMayBeForwardRefExpression(new LiteralExpr(null), ForwardRefHandling.None),
166166
};
167167
} else if (decorator.args.length === 1) {
168168
const metaNode = decorator.args[0];
@@ -180,7 +180,7 @@ function extractInjectableMetadata(
180180

181181
const providedIn = meta.has('providedIn') ?
182182
getProviderExpression(meta.get('providedIn')!, reflector) :
183-
createMayBeForwardRefExpression(new LiteralExpr(null), false);
183+
createMayBeForwardRefExpression(new LiteralExpr(null), ForwardRefHandling.None);
184184

185185
let deps: R3DependencyMetadata[]|undefined = undefined;
186186
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
@@ -223,7 +223,8 @@ function getProviderExpression(
223223
expression: ts.Expression, reflector: ReflectionHost): MaybeForwardRefExpression {
224224
const forwardRefValue = tryUnwrapForwardRef(expression, reflector);
225225
return createMayBeForwardRefExpression(
226-
new WrappedNodeExpr(forwardRefValue ?? expression), forwardRefValue !== null);
226+
new WrappedNodeExpr(forwardRefValue ?? expression),
227+
forwardRefValue !== null ? ForwardRefHandling.Unwrapped : ForwardRefHandling.None);
227228
}
228229

229230
function extractInjectableCtorDeps(

0 commit comments

Comments
 (0)