fix(ivy): don't track identifiers of ffr-resolved references (#29387)
This fix is for a bug in the ngtsc PartialEvaluator, which statically evaluates expressions. Sometimes, evaluating a reference requires resolving a function which is declared in another module, and thus no function body is available. To support this case, the PartialEvaluator has the concept of a foreign function resolver. This allows the interpretation of expressions like: const router = RouterModule.forRoot([]); even though the definition of the 'forRoot' function has no body. In ngtsc today, this will be resolved to a Reference to RouterModule itself, via the ModuleWithProviders foreign function resolver. However, the PartialEvaluator also associates any Identifiers in the path of this resolution with the Reference. This is done so that if the user writes const x = imported.y; 'x' can be generated as a local identifier instead of adding an import for 'y'. This was at the heart of a bug. In the above case with 'router', the PartialEvaluator added the identifier 'router' to the Reference generated (through FFR) to RouterModule. This is not correct. References that result from FFR expressions may not have the same value at runtime as they do at compile time (indeed, this is not the case for ModuleWithProviders). The Reference generated via FFR is "synthetic" in the sense that it's constructed based on a useful interpretation of the code, not an accurate representation of the runtime value. Therefore, it may not be legal to refer to the Reference via the 'router' identifier. This commit adds the ability to mark such a Reference as 'synthetic', which allows the PartialEvaluator to not add the 'router' identifier down the line. Tests are included for both the PartialEvaluator itself as well as the resultant buggy behavior in ngtsc overall. PR Close #29387
This commit is contained in:

committed by
Matias Niemelä

parent
ce4da3f8e5
commit
ae4a86e3b5
@ -1251,6 +1251,36 @@ describe('ngtsc behavioral tests', () => {
|
||||
.toContain(
|
||||
'i0.ɵNgModuleDefWithMeta<TestModule, never, [typeof i1.InternalRouterModule], never>');
|
||||
});
|
||||
|
||||
it('should not reference a constant with a ModuleWithProviders value in ngModuleDef imports',
|
||||
() => {
|
||||
env.tsconfig();
|
||||
env.write('dep.d.ts', `
|
||||
import {ModuleWithProviders, ɵNgModuleDefWithMeta as NgModuleDefWithMeta} from '@angular/core';
|
||||
|
||||
export declare class DepModule {
|
||||
static forRoot(arg1: any, arg2: any): ModuleWithProviders<DepModule>;
|
||||
static ngModuleDef: NgModuleDefWithMeta<DepModule, never, never, never>;
|
||||
}
|
||||
`);
|
||||
env.write('test.ts', `
|
||||
import {NgModule, ModuleWithProviders} from '@angular/core';
|
||||
import {DepModule} from './dep';
|
||||
|
||||
@NgModule({})
|
||||
export class Base {}
|
||||
|
||||
const mwp = DepModule.forRoot(1,2);
|
||||
|
||||
@NgModule({
|
||||
imports: [mwp],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('imports: [i1.DepModule]');
|
||||
});
|
||||
});
|
||||
|
||||
it('should unwrap a ModuleWithProviders-like function if a matching literal type is provided for it',
|
||||
|
Reference in New Issue
Block a user