Skip to content

Commit 06050ac

Browse files
JoostKdylhunn
authored andcommitted
fix(compiler-cli): handle inline type-check blocks in nullish coalescing extended check (#45454)
This commit fixes an inconsistency where a type check location for an inline type check block would be interpreted to occur in a type-checking shim instead. This resulted in a missing template mapping, causing a crash due to an unsafe non-null assertion operator. In the prior commit the `TcbLocation` has been extended with an `isShimFile` field that is now being used to look for the template mapping in the correct location. Additionally, the non-null assertion operator is refactored such that a missing template mapping will now ignore the warning instead of crashing the compiler. Fixes #45413 PR Close #45454
1 parent ff32301 commit 06050ac

File tree

3 files changed

+57
-9
lines changed

3 files changed

+57
-9
lines changed

packages/compiler-cli/src/ngtsc/typecheck/extended/checks/nullish_coalescing_not_nullable/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,13 @@ class NullishCoalescingNotNullableCheck extends
5050
if (symbol.kind !== SymbolKind.Expression) {
5151
return [];
5252
}
53-
const span = ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(symbol.tcbLocation)!.span;
53+
const templateMapping =
54+
ctx.templateTypeChecker.getTemplateMappingAtTcbLocation(symbol.tcbLocation);
55+
if (templateMapping === null) {
56+
return [];
57+
}
5458
const diagnostic = ctx.makeTemplateDiagnostic(
55-
span,
59+
templateMapping.span,
5660
`The left side of this nullish coalescing operation does not include 'null' or 'undefined' in its type, therefore the '??' operator can be safely removed.`);
5761
return [diagnostic];
5862
}

packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/nullish_coalescing_not_nullable/nullish_coalescing_not_nullable_spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ runInEachFileSystem(() => {
6767
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`var1 ?? 'foo'`);
6868
});
6969

70+
it('should produce nullish coalescing warning for classes with inline TCBs', () => {
71+
const fileName = absoluteFrom('/main.ts');
72+
const {program, templateTypeChecker} = setup(
73+
[{
74+
fileName,
75+
templates: {
76+
'TestCmp': `{{ var1 ?? 'foo' }}`,
77+
},
78+
source: 'class TestCmp { var1: string = "text"; }'
79+
}],
80+
{inlining: true});
81+
const sf = getSourceFileOrError(program, fileName);
82+
const component = getClass(sf, 'TestCmp');
83+
const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
84+
templateTypeChecker, program.getTypeChecker(), [nullishCoalescingNotNullableFactory],
85+
{strictNullChecks: true} /* options */);
86+
const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
87+
expect(diags.length).toBe(1);
88+
expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
89+
expect(diags[0].code).toBe(ngErrorCode(ErrorCode.NULLISH_COALESCING_NOT_NULLABLE));
90+
expect(getSourceCodeForDiagnostic(diags[0])).toBe(`var1 ?? 'foo'`);
91+
});
92+
7093
it('should not produce nullish coalescing warning for a nullable type', () => {
7194
const fileName = absoluteFrom('/main.ts');
7295
const {program, templateTypeChecker} = setup([{

packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,28 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
146146
return this.getFileAndShimRecordsForPath(filePath) !== null;
147147
}
148148

149+
private getFileRecordForTcbLocation({tcbPath, isShimFile}: TcbLocation): FileTypeCheckingData
150+
|null {
151+
if (!isShimFile) {
152+
// The location is not within a shim file but corresponds with an inline TCB in an original
153+
// source file; we can obtain the record directly by its path.
154+
if (this.state.has(tcbPath)) {
155+
return this.state.get(tcbPath)!;
156+
} else {
157+
return null;
158+
}
159+
}
160+
161+
// The location is within a type-checking shim file; find the type-checking data that owns this
162+
// shim path.
163+
const records = this.getFileAndShimRecordsForPath(tcbPath);
164+
if (records !== null) {
165+
return records.fileRecord;
166+
} else {
167+
return null;
168+
}
169+
}
170+
149171
private getFileAndShimRecordsForPath(shimPath: AbsoluteFsPath):
150172
{fileRecord: FileTypeCheckingData, shimRecord: ShimTypeCheckingData}|null {
151173
for (const fileRecord of this.state.values()) {
@@ -156,20 +178,19 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
156178
return null;
157179
}
158180

159-
getTemplateMappingAtTcbLocation({tcbPath, positionInFile}: TcbLocation): FullTemplateMapping
160-
|null {
161-
const records = this.getFileAndShimRecordsForPath(absoluteFrom(tcbPath));
162-
if (records === null) {
181+
getTemplateMappingAtTcbLocation(tcbLocation: TcbLocation): FullTemplateMapping|null {
182+
const fileRecord = this.getFileRecordForTcbLocation(tcbLocation);
183+
if (fileRecord === null) {
163184
return null;
164185
}
165-
const {fileRecord} = records;
166186

167-
const shimSf = this.programDriver.getProgram().getSourceFile(absoluteFrom(tcbPath));
187+
const shimSf = this.programDriver.getProgram().getSourceFile(tcbLocation.tcbPath);
168188
if (shimSf === undefined) {
169189
return null;
170190
}
171191
return getTemplateMapping(
172-
shimSf, positionInFile, fileRecord.sourceManager, /*isDiagnosticsRequest*/ false);
192+
shimSf, tcbLocation.positionInFile, fileRecord.sourceManager,
193+
/*isDiagnosticsRequest*/ false);
173194
}
174195

175196
generateAllTypeCheckBlocks() {

0 commit comments

Comments
 (0)