New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(ivy): handle modifiedResourceFiles being null on incremental compilation #31322
Conversation
@@ -112,7 +112,8 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta | |||
} | |||
|
|||
private hasChangedResourceDependencies(sf: ts.SourceFile): boolean { | |||
if (this.modifiedResourceFiles === undefined || !this.metadata.has(sf)) { | |||
if (this.modifiedResourceFiles === undefined || this.modifiedResourceFiles === null || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (this.modifiedResourceFiles === undefined || this.modifiedResourceFiles === null || | |
if (this.modifiedResourceFiles == null || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type doesn't even allow for undefined, so I guess you could just check for null
with triple equals or fix the type if it can really be undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure, I guess that undefined check is there for a reason, let's see what @alxhub / @petebacondarwin say
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! This is my mistake. Sorry.
In the original draft of the PR the parameter was optional, which meant that it could be undefined
or defined (not null
) but then I made it non-optional and switched to using null
to indicate that it is explicitly not needed.
So we should be able ditch the undefined
check in favour of the null
check.
We should also add tests for this bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattlewis92 - thanks for catching this.
Please just replace the === undefined
with === null
.
Do you fancy trying to add a test for this case to https://github.com/angular/angular/blob/master/packages/compiler-cli/test/ngtsc/incremental_spec.ts ?
If not, let me know and I can take over this PR and add the test.
Yeah sure, I can try writing a test. If I understand the problem right, I can copy from this test: https://github.com/angular/angular/blob/master/packages/compiler-cli/test/ngtsc/incremental_spec.ts#L79-L104 but then make a change to |
@mattlewis92 - yes copy the test not modify ;-) and make the change as you suggest. Please check that this will fail before the fix is applied. Get in touch if you have difficulty running the tests or getting it to fail. Thanks! |
@petebacondarwin I'm struggling to get this to fail as I can never get |
😱 do you know what trigger in your project causes the error? |
It was the initial load of the app, before the incremental compilation would have even triggered, which makes sense actually. |
But the CLI implementation of that function always returns a |
OK, so ngcc converts any falsy value to
So perhaps the |
Ahah! The test is at fault. See
|
Ah, I'm using the 8.0.x version of the cli which doesn't have that method implemented so I guess it falls back to the default one provided by ngtsc, which would return undefined on the first run and then get converted to null by this:
|
If we change that param to |
So CLI is doing the right thing (even 8.0.0 version). |
I tried changing it to
|
I'm just playing with it now... |
try these changes: diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts
index 13ca9d5c22..7e03a4ecb3 100644
--- a/packages/compiler-cli/src/main.ts
+++ b/packages/compiler-cli/src/main.ts
@@ -26,7 +26,7 @@ export function main(
config?: NgcParsedConfiguration, customTransformers?: api.CustomTransformers, programReuse?: {
program: api.Program | undefined,
},
- modifiedResourceFiles?: Set<string>): number {
+ modifiedResourceFiles: Set<string>| null = null): number {
let {project, rootNames, options, errors: configErrors, watch, emitFlags} =
config || readNgcCommandLineAndConfiguration(args);
if (configErrors.length) {
diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts
index 4d27b2b323..f7ea7bbb59 100644
--- a/packages/compiler-cli/src/perform_compile.ts
+++ b/packages/compiler-cli/src/perform_compile.ts
@@ -224,7 +224,7 @@ export function exitCodeFromResult(diags: Diagnostics | undefined): number {
export function performCompilation(
{rootNames, options, host, oldProgram, emitCallback, mergeEmitResultsCallback,
gatherDiagnostics = defaultGatherDiagnostics, customTransformers,
- emitFlags = api.EmitFlags.Default, modifiedResourceFiles}: {
+ emitFlags = api.EmitFlags.Default, modifiedResourceFiles = null}: {
rootNames: string[],
options: api.CompilerOptions,
host?: api.CompilerHost,
@@ -234,7 +234,7 @@ export function performCompilation(
gatherDiagnostics?: (program: api.Program) => Diagnostics,
customTransformers?: api.CustomTransformers,
emitFlags?: api.EmitFlags,
- modifiedResourceFiles?: Set<string>,
+ modifiedResourceFiles?: Set<string>| null,
}): PerformCompilationResult {
let program: api.Program|undefined;
let emitResult: ts.EmitResult|undefined;
diff --git a/packages/compiler-cli/test/ngtsc/env.ts b/packages/compiler-cli/test/ngtsc/env.ts
index efb1fe6793..1f1639688d 100644
--- a/packages/compiler-cli/test/ngtsc/env.ts
+++ b/packages/compiler-cli/test/ngtsc/env.ts
@@ -26,7 +26,7 @@ import {setWrapHostForTest} from '../../src/transformers/compiler_host';
export class NgtscTestEnvironment {
private multiCompileHostExt: MultiCompileHostExt|null = null;
private oldProgram: Program|null = null;
- private changedResources: Set<string>|undefined = undefined;
+ private changedResources: Set<string>|null = null;
private constructor(
private fs: FileSystem, readonly outDir: AbsoluteFsPath, readonly basePath: AbsoluteFsPath) {}
@@ -107,6 +107,12 @@ export class NgtscTestEnvironment {
this.multiCompileHostExt.flushWrittenFileTracking();
}
+ /**
+ * Older versions of the CLI do not provide the `CompilerHost.getModifiedResourceFiles()` method.
+ * This results in the `changedResources` set being `null`.
+ */
+ simulateLegacyCLICompilerHost() { this.changedResources = null; }
+
getFilesWrittenSinceLastFlush(): Set<string> {
if (this.multiCompileHostExt === null) {
throw new Error(`Not tracking written files - call enableMultipleCompilations()`);
diff --git a/packages/compiler-cli/test/ngtsc/incremental_spec.ts b/packages/compiler-cli/test/ngtsc/incremental_spec.ts
index 9cf9198feb..b4daca54ff 100644
--- a/packages/compiler-cli/test/ngtsc/incremental_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/incremental_spec.ts
@@ -23,6 +23,24 @@ runInEachFileSystem(() => {
env.tsconfig();
});
+ it('should not crash if CLI does not provide getModifiedResourceFiles()', () => {
+ env.write('component1.ts', `
+ import {Component} from '@angular/core';
+
+ @Component({selector: 'cmp', templateUrl: './component1.template.html'})
+ export class Cmp1 {}
+ `);
+ env.write('component1.template.html', 'cmp1');
+ env.driveMain();
+
+ // Simulate a change to `component1.html`
+ env.flushWrittenFileTracking();
+ env.invalidateCachedFile('component1.html');
+ env.simulateLegacyCLICompilerHost();
+ env.driveMain();
+ });
+
it('should skip unchanged services', () => {
env.write('service.ts', `
import {Injectable} from '@angular/core'; |
0b6a0b1
to
a8ada6b
Compare
a8ada6b
to
7e6c38c
Compare
Awesome, that got it! Thanks so much for your help! 😄 |
Thanks for this @mattlewis92 - @alxhub is away at the moment but once he is back, we can get his approval and get this merged. |
Hi @mattlewis92, This looks good to me! The only thing that I would ask is that you add some detail to the commit message describing the bug and the fix. In particular I'm interested in how this manages to escape type checking - how do we end up with |
@petebacondarwin please can you help me out with what to put for the commit message? Thanks! 😄 |
@@ -112,7 +112,7 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta | |||
} | |||
|
|||
private hasChangedResourceDependencies(sf: ts.SourceFile): boolean { | |||
if (this.modifiedResourceFiles === undefined || !this.metadata.has(sf)) { | |||
if (this.modifiedResourceFiles === null || !this.metadata.has(sf)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alxhub this is the key change. The IncrementalState
class takes a Set<string>|null
for its modifiedResourceFiles
parameter. The modifiedResourceFiles
property could never be undefined
, instead it could be a Set<string>
or it could be null
.
To tighten up the types across the system we changed everything else so that all properties and variables that could hold this modified resource files value could also never by undefined
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative approach would have been to change every type to Set<string>|undefined
instead. But my understanding is that you prefer to use null
when something is explicitly not being provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@mattlewis92 - I would suggest something like the following for the commit message...
|
…ed files Versions of CLI prior to angular/angular-cli@0e339ee did not expose the host.getModifiedResourceFiles() method. This meant that null was being passed through to the IncrementalState.reconcile() method to indicate that there were either no changes or the host didn't support that method. This commit fixes a bug where we were checking for undefined rather than null when deciding whether any resource files had changed, causing a null reference error to be thrown. This bug was not caught by the unit testing because the tests set up the changed files via a slightly different process, not having access to the CompilerHost, and these test were making the erroneous assumption that undefined indicated that there were no changed files.
7e6c38c
to
c470579
Compare
Awesome, thanks @petebacondarwin! I've just made that change now. |
I got the same error, but not with CLI, but just by calling ngc in watch mode (with "enableIvy": true). |
@stefanocke ivy fixes don't get backported to 8.1.x, so you need to be on |
…ed files (angular#31322) Versions of CLI prior to angular/angular-cli@0e339ee did not expose the host.getModifiedResourceFiles() method. This meant that null was being passed through to the IncrementalState.reconcile() method to indicate that there were either no changes or the host didn't support that method. This commit fixes a bug where we were checking for undefined rather than null when deciding whether any resource files had changed, causing a null reference error to be thrown. This bug was not caught by the unit testing because the tests set up the changed files via a slightly different process, not having access to the CompilerHost, and these test were making the erroneous assumption that undefined indicated that there were no changed files. PR Close angular#31322
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Incremental compilation fails with ivy:
What is the new behavior?
Incremental compilation works
Does this PR introduce a breaking change?
Other information
I applied this patch locally and everything worked smoothly after! 😄