fix(ivy): use any for generic context checks when !strictTemplates (#34649)
Previously, the template type-checker would always construct a generic template context type with correct bounds, even when strictTemplates was disabled. This meant that type-checking of expressions involving that type was stricter than View Engine. This commit introduces a 'strictContextGenerics' flag which behaves similarly to other 'strictTemplates' flags, and switches the inference of generic type parameters on the component context based on the value of this flag. PR Close #34649
This commit is contained in:
parent
cb11380515
commit
0c8d085666
@ -120,6 +120,7 @@ In case of a false positive like these, there are a few options:
|
|||||||
|`strictDomLocalRefTypes`|Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for `<input #ref>`.|
|
|`strictDomLocalRefTypes`|Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for `<input #ref>`.|
|
||||||
|`strictOutputEventTypes`|Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`.|
|
|`strictOutputEventTypes`|Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`.|
|
||||||
|`strictDomEventTypes`|Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`.|
|
|`strictDomEventTypes`|Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`.|
|
||||||
|
|`strictContextGenerics`|Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be `any`.|
|
||||||
|
|
||||||
|
|
||||||
If you still have issues after troubleshooting with these flags, you can fall back to full mode by disabling `strictTemplates`.
|
If you still have issues after troubleshooting with these flags, you can fall back to full mode by disabling `strictTemplates`.
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
export {AliasStrategy, AliasingHost, FileToModuleAliasingHost, PrivateExportAliasingHost} from './src/alias';
|
export {AliasStrategy, AliasingHost, FileToModuleAliasingHost, PrivateExportAliasingHost} from './src/alias';
|
||||||
export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAndRewriteCoreSymbol} from './src/core';
|
export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAndRewriteCoreSymbol} from './src/core';
|
||||||
export {DefaultImportRecorder, DefaultImportTracker, NOOP_DEFAULT_IMPORT_RECORDER} from './src/default';
|
export {DefaultImportRecorder, DefaultImportTracker, NOOP_DEFAULT_IMPORT_RECORDER} from './src/default';
|
||||||
export {AbsoluteModuleStrategy, FileToModuleHost, FileToModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy} from './src/emitter';
|
export {AbsoluteModuleStrategy, FileToModuleHost, FileToModuleStrategy, ImportFlags, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy} from './src/emitter';
|
||||||
export {Reexport} from './src/reexport';
|
export {Reexport} from './src/reexport';
|
||||||
export {ImportFlags, OwningModule, Reference} from './src/references';
|
export {OwningModule, Reference} from './src/references';
|
||||||
export {ModuleResolver} from './src/resolver';
|
export {ModuleResolver} from './src/resolver';
|
||||||
|
@ -504,6 +504,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
// Pipes are checked in View Engine so there is no strictness flag.
|
// Pipes are checked in View Engine so there is no strictness flag.
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
strictSafeNavigationTypes: strictTemplates,
|
strictSafeNavigationTypes: strictTemplates,
|
||||||
|
useContextGenericType: strictTemplates,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
typeCheckingConfig = {
|
typeCheckingConfig = {
|
||||||
@ -521,6 +522,7 @@ export class NgtscProgram implements api.Program {
|
|||||||
checkTypeOfNonDomReferences: false,
|
checkTypeOfNonDomReferences: false,
|
||||||
checkTypeOfPipes: false,
|
checkTypeOfPipes: false,
|
||||||
strictSafeNavigationTypes: false,
|
strictSafeNavigationTypes: false,
|
||||||
|
useContextGenericType: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,6 +551,9 @@ export class NgtscProgram implements api.Program {
|
|||||||
if (this.options.strictAttributeTypes !== undefined) {
|
if (this.options.strictAttributeTypes !== undefined) {
|
||||||
typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
|
typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
|
||||||
}
|
}
|
||||||
|
if (this.options.strictContextGenerics !== undefined) {
|
||||||
|
typeCheckingConfig.useContextGenericType = this.options.strictContextGenerics;
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the typeCheck phase of each decorator in the program.
|
// Execute the typeCheck phase of each decorator in the program.
|
||||||
const prepSpan = this.perfRecorder.start('typeCheckPrep');
|
const prepSpan = this.perfRecorder.start('typeCheckPrep');
|
||||||
|
@ -211,6 +211,15 @@ export interface TypeCheckingConfig {
|
|||||||
* This is currently an unsupported feature.
|
* This is currently an unsupported feature.
|
||||||
*/
|
*/
|
||||||
checkQueries: false;
|
checkQueries: false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use any generic types of the context component.
|
||||||
|
*
|
||||||
|
* If this is `true`, then if the context component has generic types, those will be mirrored in
|
||||||
|
* the template type-checking context. If `false`, any generic type parameters of the context
|
||||||
|
* component will be set to `any` during type-checking.
|
||||||
|
*/
|
||||||
|
useContextGenericType: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ export function generateTypeCheckBlock(
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
|
`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
|
||||||
}
|
}
|
||||||
const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName)];
|
const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName, env.config.useContextGenericType)];
|
||||||
|
|
||||||
const scopeStatements = scope.render();
|
const scopeStatements = scope.render();
|
||||||
const innerBody = ts.createBlock([
|
const innerBody = ts.createBlock([
|
||||||
@ -73,7 +73,7 @@ export function generateTypeCheckBlock(
|
|||||||
/* modifiers */ undefined,
|
/* modifiers */ undefined,
|
||||||
/* asteriskToken */ undefined,
|
/* asteriskToken */ undefined,
|
||||||
/* name */ name,
|
/* name */ name,
|
||||||
/* typeParameters */ ref.node.typeParameters,
|
/* typeParameters */ env.config.useContextGenericType ? ref.node.typeParameters : undefined,
|
||||||
/* parameters */ paramList,
|
/* parameters */ paramList,
|
||||||
/* type */ undefined,
|
/* type */ undefined,
|
||||||
/* body */ body);
|
/* body */ body);
|
||||||
@ -925,12 +925,18 @@ class Scope {
|
|||||||
* parameters listed (without their generic bounds).
|
* parameters listed (without their generic bounds).
|
||||||
*/
|
*/
|
||||||
function tcbCtxParam(
|
function tcbCtxParam(
|
||||||
node: ClassDeclaration<ts.ClassDeclaration>, name: ts.EntityName): ts.ParameterDeclaration {
|
node: ClassDeclaration<ts.ClassDeclaration>, name: ts.EntityName,
|
||||||
|
useGenericType: boolean): ts.ParameterDeclaration {
|
||||||
let typeArguments: ts.TypeNode[]|undefined = undefined;
|
let typeArguments: ts.TypeNode[]|undefined = undefined;
|
||||||
// Check if the component is generic, and pass generic type parameters if so.
|
// Check if the component is generic, and pass generic type parameters if so.
|
||||||
if (node.typeParameters !== undefined) {
|
if (node.typeParameters !== undefined) {
|
||||||
typeArguments =
|
if (useGenericType) {
|
||||||
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined));
|
typeArguments =
|
||||||
|
node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined));
|
||||||
|
} else {
|
||||||
|
typeArguments =
|
||||||
|
node.typeParameters.map(() => ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const type = ts.createTypeReferenceNode(name, typeArguments);
|
const type = ts.createTypeReferenceNode(name, typeArguments);
|
||||||
return ts.createParameter(
|
return ts.createParameter(
|
||||||
|
@ -163,6 +163,7 @@ export const ALL_ENABLED_CONFIG: TypeCheckingConfig = {
|
|||||||
checkTypeOfNonDomReferences: true,
|
checkTypeOfNonDomReferences: true,
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
strictSafeNavigationTypes: true,
|
strictSafeNavigationTypes: true,
|
||||||
|
useContextGenericType: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
|
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
|
||||||
@ -217,6 +218,7 @@ export function tcb(
|
|||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
checkTemplateBodies: true,
|
checkTemplateBodies: true,
|
||||||
strictSafeNavigationTypes: true,
|
strictSafeNavigationTypes: true,
|
||||||
|
useContextGenericType: true,
|
||||||
};
|
};
|
||||||
options = options || {
|
options = options || {
|
||||||
emitSpans: false,
|
emitSpans: false,
|
||||||
|
@ -326,6 +326,7 @@ describe('type check blocks', () => {
|
|||||||
checkTypeOfNonDomReferences: true,
|
checkTypeOfNonDomReferences: true,
|
||||||
checkTypeOfPipes: true,
|
checkTypeOfPipes: true,
|
||||||
strictSafeNavigationTypes: true,
|
strictSafeNavigationTypes: true,
|
||||||
|
useContextGenericType: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('config.applyTemplateContextGuards', () => {
|
describe('config.applyTemplateContextGuards', () => {
|
||||||
@ -567,5 +568,20 @@ describe('type check blocks', () => {
|
|||||||
expect(block).toContain('(((ctx).a) != null ? ((ctx).a)!.b : null as any)');
|
expect(block).toContain('(((ctx).a) != null ? ((ctx).a)!.b : null as any)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('config.strictContextGenerics', () => {
|
||||||
|
const TEMPLATE = `Test`;
|
||||||
|
|
||||||
|
it('should use the generic type of the context when enabled', () => {
|
||||||
|
const block = tcb(TEMPLATE);
|
||||||
|
expect(block).toContain('function Test_TCB<T extends string>(ctx: Test<T>)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use any for the context generic type when disabled', () => {
|
||||||
|
const DISABLED_CONFIG: TypeCheckingConfig = {...BASE_CONFIG, useContextGenericType: false};
|
||||||
|
const block = tcb(TEMPLATE, undefined, DISABLED_CONFIG);
|
||||||
|
expect(block).toContain('function Test_TCB(ctx: Test<any>)');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -215,6 +215,19 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
|||||||
*/
|
*/
|
||||||
strictDomEventTypes?: boolean;
|
strictDomEventTypes?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to include the generic type of components when type-checking the template.
|
||||||
|
*
|
||||||
|
* If no component has generic type parameters, this setting has no effect.
|
||||||
|
*
|
||||||
|
* If a component has generic type parameters and this setting is `true`, those generic parameters
|
||||||
|
* will be included in the context type for the template. If `false`, any generic parameters will
|
||||||
|
* be set to `any` in the template context type.
|
||||||
|
*
|
||||||
|
* Defaults to `false`, even if "fullTemplateTypeCheck" is set.
|
||||||
|
*/
|
||||||
|
strictContextGenerics?: boolean;
|
||||||
|
|
||||||
// Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
// Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
||||||
// import module specifiers. This is false by default, and exists to support running ngtsc
|
// import module specifiers. This is false by default, and exists to support running ngtsc
|
||||||
// within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
// within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
||||||
|
@ -972,7 +972,8 @@ export declare class AnimationEvent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should constrain types using type parameter bounds', () => {
|
it('should constrain types using type parameter bounds', () => {
|
||||||
env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true});
|
env.tsconfig(
|
||||||
|
{fullTemplateTypeCheck: true, strictInputTypes: true, strictContextGenerics: true});
|
||||||
env.write('test.ts', `
|
env.write('test.ts', `
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component, Input, NgModule} from '@angular/core';
|
import {Component, Input, NgModule} from '@angular/core';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user