-
Notifications
You must be signed in to change notification settings - Fork 232
Dart2: Collection literals in templates cause type cast failure #844
Comments
Just for clarification, this would be "simpler" if we removed proxying literals, right @leonsenft? (Albeit, with potential performance consequences) |
Can you elaborate more on what exactly you have in mind if we removed proxy literals? A few ideas come to mind (mostly terrible).
|
Too many choices! I have choice paralysis!
We should gather a bit of data for what the impact of this would be, in case we run out of options and need to go down this route. I don't think it's the best option today, but depending on the date of Dart2 we might need to at least consider it.
Yeah, seems difficult to land, and I'd be worried about usage. That being said, again, it depends on the amount of usage today.
This doesn't seem terrible, especially since it more or less keeps the same contract, and we could always optimize again in the future when we aren't under a deadline.
This seems better than
I'm not a fan of the suggestion above, specifically creating "pseudo" properties that only the template compiler can see, but something like this has been requested by users in the past. What about a sort of combination of this and class Comp {
String color = 'blue';
@Computed.from(const [#color])
Map<String, String> get styles => {'color': color};
} ... with the following definition ... /// An annotation that defines a getter or method as dependent on external fields.
///
/// The AngularDart compiler may, where able, reduce the amount of change detection
/// checks of the annotated getter or method to when the provided fields change. For
/// example:
/// ```
/// class Comp {
/// String color = 'blue';
///
/// @Computed.from(const [#color])
/// Map<String, String> get styles => {'color': color};
/// }
/// ```
abstract class Computed {
const factory Computed.from(List<Object> properties) = ...
} This does require more thought, for example if you could have computed fields that rely on other computed fields, how would cycles work, etc, but could be an nice "carrot" to offer if we end up having to remove literal support in the templates. FWIW, this overlaps a bit with:
|
Also @leonsenft, nice use of emojis 👏 |
Agreed.
In hindsight I think this (2.) is by far the worst option. Better to temporarily bloat code size (3.), than to fundamentally break Angular's change detection contract.
Good point, and agreed. Agree with you across the board on (5.). Your solution is much more elegant than mine! What advantage does using a If we're going to seriously consider this option we should file a new issue to discuss it, but I think most of your concerns are addressable. For example, a computed property could rely on another, but we'd need to produce a compile error when we detect a cycle (like we already do for dependency injection!). |
@leonsenft is investigating the cost of more bloated code on our large internal clients. |
There are two additional options not yet outlined.
|
Just my 2c
Inference based on the Input type seems pretty reasonable - it's not like it will work at runtime if the types are wrong anyway. I've never in two years of Angular Dart seen someone pass a literal in a template through a method or closure. Though that doesn't mean people aren't using it of course, but it doesn't seem like an edge people would hit.
This seems like a can of worms, possibly the same can of worms as passing generics to templates/components themselves. |
Yeah, ideally this ends up being the overwhelmingly common case, which gives us flexibility in choosing a rarely generated fallback option. |
I probably don't have enough context to contribute anything meaningful to this discussion, but I'm failing to see how types affect performance at all. I'm assuming you are making a copy of the collection as a snapshot for a future change detection cycle. AFAICT, change detection does not call any methods on the elements of the collection, but merely compares them using |
To clarify, by "not needing generic types" I mean that |
@yjbanov Typing isn't about performance, but rather making templates sound. Left as they are, they'll cease to work in Dart 2. Any concern over performance is in regards to the solution, since some have performance implications. For example we can always fallback to local inference in the absence of knowing the type annotation to generate (solution 3.), but this would seriously bloat the generated code. |
Gotcha. If I may throw in an option, maybe another take on inference can work: 8. Inferred collection type class ViewAppComponent0 {
// Compiler already supports map literals natively, so
// it can create specialized code for maps.
Map<String, String> _map_0 = {};
...
ComponentRef<AppComponent> build() {
// no extra code here
}
void detectChangesInternal() {
...
_map_0['color'] = _ctx.color;
_map_0['background'] = _ctx.background;
if (!compareMaps(_map_0, _NgStyle_0_4.rawStyle)) {
_NgStyle_0_4.rawStyle = _map_0;
_map_0 = {};
}
...
}
} |
The issue is we don't always have a way of easily determining the type of the map. Your solution is effectively equivalent to 6 in terms of the analysis required on the compiler's behalf. |
Worth noting, this is a test case that fails in DDC with errors enabled: ... I'll |
We plan to move forward with a partial implementation of option 6. The type of the input to which a collection literal is bound will be used to type annotate the generated code. This has two important implications that may be breaking changes for users.
The reasoning for this decision is based on two factors.
If anyone anticipates this to be a large setback for them, please let us know here or file another issue requesting full support be reinstated through other means. |
…on fields A cast issue arose when a collection literal was bound to an input. <div [ngStyle]="{'color': color}"> This would generate code similar to the following: class ViewAppComponent { NgStyle _NgStyle; var _map; var _expr; build() { ... // Returns a Map<String, dynamic>. _map = pureProxy1((p) { return {'background': p}; }); } void detectChangesInternal() { ... final currVal = _map(_ctx.color); if (!identical(_expr, currVal)) { // Cast warning: Map<String, dynamic> assigned to Map<String, String> _NgStyle.rawStyle = currVal; _expr = currval; } } } Since we know `[ngStyle]` is expecting something assignable to `Map<String, String>`, we can propagate this type information forward to fields associated with its binding. class ViewAppComponent { NgStyle _NgStyle; Map<String, String> Function(String) _map; ... } Closes #844. PiperOrigin-RevId: 187631882
…on fields A cast issue arose when a collection literal was bound to an input. <div [ngStyle]="{'color': color}"> This would generate code similar to the following: class ViewAppComponent { NgStyle _NgStyle; var _map; var _expr; build() { ... // Returns a Map<String, dynamic>. _map = pureProxy1((p) { return {'background': p}; }); } void detectChangesInternal() { ... final currVal = _map(_ctx.color); if (!identical(_expr, currVal)) { // Cast warning: Map<String, dynamic> assigned to Map<String, String> _NgStyle.rawStyle = currVal; _expr = currval; } } } Since we know `[ngStyle]` is expecting something assignable to `Map<String, String>`, we can propagate this type information forward to fields associated with its binding. class ViewAppComponent { NgStyle _NgStyle; Map<String, String> Function(String) _map; ... } Closes #844. PiperOrigin-RevId: 187631882
I still have this issue in angular 5.0.0 EXCEPTION: Type 'IdentityMap<String, dynamic>' should be 'IdentityMap<String, String>' to implement expected type 'Map<String, String>'. |
You will need to file another bug/repro case, it's not possible for us to monitor closed bugs to help. |
@fmatuszewski Yes, please file another issue, and include the template HTML where this error is occurring; it's not possible for us to tell what's causing this issue without seeing the source. |
Uh oh!
There was an error while loading. Please reload this page.
Currently in DDC, binding a collection literal in a template where a typed collection is expected
generates
In Dart 2, this will become an error.
The culprit is the use of a
pureProxyN
function to change detect the bound values in the collection and build a new collection each time one changes.https://github.com/dart-lang/angular/blob/0f6db9823667f13bd772425caa89321d5ff268d5/angular/lib/src/core/linker/app_view_utils.dart#L325-L337
While this function does have generic type parameters, the generated code doesn't use it in a context where they can be inferred, nor do we have the information to populate them manually.
This is an example of what the code generated for the above template looks like, with comments explaining the issue.
Type annotating the
_map_0
field isn't always feasible, since it requires analyzing arbitrary expressions in the template. Likewise, determining the types bound in the collection pose a similar challenge.The text was updated successfully, but these errors were encountered: