TypeScript: Design:type metadata for cyclic dependencies throw at runtime
TypeScript Version: 3.2.0-dev.20181003
Search Terms: design cyclic
Code
index.ts
import 'reflect-metadata';
function property() {
return (target: Object, key: string) => {
const t = Reflect.getMetadata('design:type', target, key);
console.log('property %s#%s of type', target.constructor.name, key, t.name);
}
}
class Product {
// belongs to a category
@property()
category: Category;
}
class Category {
// has many products
@property()
products: Product[];
}
tsconfig.json
{
"$schema": "http://json.schemastore.org/tsconfig",
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2018", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"target": "es2017"
},
"include": [
"index.ts"
]
}
package.json
{
"dependencies": {
"reflect-metadata": "^0.1.12",
"typescript": "^3.2.0-dev.20181003"
}
}
Expected behavior:
Ideally, the code compiles and node index.js produces the following console output:
property Product#category of type Category
property Category#products of type Array
If this is not possible, then the compiler should detect cyclic dependencies and fail the compilation with a helpful error.
Actual behavior:
The code compiles. When the compiled code is executed, it fails at runtime.
index.js:23
__metadata("design:type", Category)
^
ReferenceError: Category is not defined
at Object.<anonymous> (index.js:23:31)
at Module._compile (internal/modules/cjs/loader.js:689:30)
(...)
Here is the relevant snippet from the transpiled output:
class Product {
}
__decorate([
property(),
__metadata("design:type", Category)
], Product.prototype, "category", void 0);
class Category {
}
__decorate([
property(),
__metadata("design:type", Array)
], Category.prototype, "products", void 0);
Playground Link:
Playground does not support emitDecoratorMetadata and experimentalDecorators options ☹️
Related Issues:
None found.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 16
- Comments: 15 (4 by maintainers)
Links to this issue
Commits related to this issue
- fix(core): reenable decorator downleveling for Angular npm packages In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components ... — committed to IgorMinar/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components ... — committed to IgorMinar/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components ... — committed to IgorMinar/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components ... — committed to IgorMinar/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and components ... — committed to IgorMinar/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages (#37317) In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and co... — committed to angular/angular by IgorMinar 4 years ago
- fix(core): reenable decorator downleveling for Angular npm packages (#37317) In #37221 we disabled tsickle passes from transforming the tsc output that is used to publish all Angular framework and co... — committed to angular/angular by IgorMinar 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
- fix(compiler-cli): downlevel angular decorators to static properties In v7 of Angular we removed `tsickle` from the default `ngc` pipeline. This had the negative potential of breaking ES2015 output a... — committed to devversion/angular by devversion 4 years ago
@rbuckton Any update on this?
I don’t really understand the reason why typescript emit “Type” instead of “() => Type” since the latter resolves some cyclic dependencies problems.
To me it sounds like a tiny change in an experimental feature anyway, and this tiny change would improve a lot of TS projects, and of course typescript itself.
Debugging circular import problem, when one is not aware of theses subtleties, is very hard.
Note: I spent some time yesterday making a minimal reproducible code and describing the issue in clear term here: https://github.com/microsoft/TypeScript/issues/41201.
Personally, I can live with with the limitation, i.e. that TypeScript cannot emit design-type metadata when the property is using a type before it’s declared.
However, I’d like the problem to be detected and reported by the TypeScript compiler, the compiler should fail the build with a descriptive error message that will make it easy for the developer to understand what they did wrong and how to fix the problem.
Compare it with the current situation, where compiler happily produces JavaScript code and the problem is discovered only at runtime, usually with a hard crash on uncaught ReferenceError. I find such behavior rather impractical - what is the compiler good for when it cannot detect incorrect code? The problem can be also difficult to diagnose and troubleshoot, especially for people not aware of this limitation. The message “ReferenceError: Lock is not defined” with a stack trace pointing somewhere deep into code added by the compiler provide very little hints on how to fix the problem.
TypeScript compile could produce code that used forwardRef, similar how Angular suggest: https://angular.io/api/core/forwardRef Note that the Angular example doesn’t work when targeting esnext precisely because of this issue. The compiler could produce:
It also applies to design:paramtypes.
Makes sense 👍
In which case: Can we please improve the compiler to detect the situation when it’s going to emit invalid code that will later crash at runtime?
Would that be considering a breaking change? Since the emitted code is going to crash at runtime anyways, if the compiler decide to fail the build instead of emitting a code that will crash, then I would expect that such change should not break any valid/working applications out there.
Thoughts?
I was looking at this again today and @alxhub brought up an interesting point: TS will emit invalid ES2015 code for valid TS regardless of circular dependencies.
Consider this code:
Using TS 3.4.5 and NodeJS v10.10.0, you can compile and run it via
npx tsc --target ES2015 --experimentalDecorators --emitDecoratorMetadata ./main.ts && node ./main.js.The TS compilation will succeed but execution will fail:
Yet there would be a compilation error if
Lockwas to be used as a value and not a type.Perhaps there should be a compilation error for Types being used before being declared, at least if
emitDecoratorMetadatais turned on. That, or it shouldn’t emit broken code.A possible solution I see is to modify the code setting design-type metadata to use a resolver/getter function instead of passing the type constructor directly, and access design-type metadata a bit later, after all classes were defined. I guess such requirement makes my proposed solution rather impractical.