Coder Social home page Coder Social logo

Comments (11)

lrhn avatar lrhn commented on September 25, 2024 1

It's not documented in the language tour, but it is mentioned in other places, for example the style guide:
Avoid writing incomplete generic types.

It is surprising and annoying, and if it wasn't for the historical reasons, it probably wouldn't be like that.
The fact that writing:

final foo = {"a": 1};
final Map foo = {"a": 1};
final Map<String, int> foo = {"a": 1};
final foo = <String, int>{"a": 1};
final Map foo = <String, int>{"a": 1};
final Map<String, int> foo = <String, int>{"a": 1};

gives different results for just the Map-typed ones is annoying, it looks like a place where inference could supply the type arguments. It isn't, for numerous reasons (Dart only infers type arguments in invocations ... and now patterns, but not in types).
And then it's doubly annoying that it defaults to dynamic instead of Object?, for said historical reasons.

But until we manage to change that, it is what you have to work with.
The strict-raw-types is a good suggestion. There shouldn't be any situation where you need to write a raw type. If you want Map<dynamic, dynamic> you can still write it explicitly.

from sdk.

eernstg avatar eernstg commented on September 25, 2024

You probably just want to avoid using a raw type like Map. Make it final Map<String, Object?> map, and there is no run-time error.

You can enable strict-raw-types and various other static analysis options in order to get a more strict analysis. Here's an example analysis_options.yaml:

analyzer:
  language:
    strict-raw-types: true
    strict-inference: true
    strict-casts: true

linter:
  rules:
    - avoid_dynamic_calls

The raw type Map used as a type annotation (for example, as the declared type of a variable) has a specific meaning: It means Map<dynamic, dynamic>.

In other situations you can have type inference, e.g., Iterable<int> xs = List.filled(5, 0); will cause type inference to make it List<int>.filled(5, 0), but when you are referring to a generic class just by name (no actual type arguments) and using it as a type annotation, the type arguments are considered to be unknown and they will be chosen based on the declared bounds (and when there are no bounds, like in Map, the chosen value is dynamic).

So map has type Map<dynamic, dynamic>, and this has some consequences. In particular, map['hello'] has type dynamic. (So you don't need ?., a plain . will do, but the invocation of a member is checked at run time and everything is accepted without checks at compile time).

In particular, toBool is assumed to be an instance member of the run-time type of map['hello']. Given that there "is" an instance member, extension members are not considered. (When we have both, the instance member always wins.)

So we're calling toBool on a String in map['name']?.toBool() and getting a run-time error because String does not have a member named toBool.

from sdk.

SaadArdati avatar SaadArdati commented on September 25, 2024

@eernstg In some large codebases like ours, we can't enable these analyzer hints because we rely on raw types in a few areas. This includes raw-types on a modified implementation of json_serializable factory functions.

The root of this issue is that the base type of a map retrieval operation is Object?, and yet the dart SDK seems to be ignoring it in this specific scenario. Your solution is offering a workaround on a potentially bigger problem.

from sdk.

BirjuVachhani avatar BirjuVachhani commented on September 25, 2024

Yeah explicitly casting it to Object? works and doesn't throw but if it can cast it to Object? than it should be able to call an extension on Object? too since it would be its super type, unless I am misunderstanding something! Can you confirm whether this would've worked before? We had this code in our codebase and it was working fine until now. I can't confirm the last working version of Dart SDK though.

My reference:
https://dart.dev/null-safety/understanding-null-safety#top-and-bottom
image

from sdk.

lrhn avatar lrhn commented on September 25, 2024

The raw type instantiates to bounds, which means dynamic, and extension methods do not work on dynamic.
Instantiate to bounds works like that for historical reasons (it was a way to make the Dart 2.0 type system viable, without breaking too much existing code that relied on things being dynamic.)

If you do:

final Map foo = { ... };

you are effectively writing

final Map<dynamic, dynamic> foo = { ... };

which means that foo['hello'] has type dynamic, and you can't call extension methods on it.

Changing it to final Map<String, Object?> foo = {...}; or just final foo = {...}; will avoid the raw type that gets instantiated to bounds, which makes foo['hello'] have type Object?, and then the extension method works.

I'm all for changing what instantiate to bounds do, but that's a larger breaking change, and won't help you any time soon.

from sdk.

SaadArdati avatar SaadArdati commented on September 25, 2024

@lrhn It seems counter-intuitive for the untyped version to default to Map<..., dynamic> while the inferred type to defaults to Map<..., Object?>, no? It's a direct and unexpected discrepancy to a developer.

This is not documented behavior even.

from sdk.

eernstg avatar eernstg commented on September 25, 2024

we rely on raw types in a few areas

You can turn off strict-raw-types by having some exclude clauses in your analysis_options.yaml or by putting // ignore: strict_raw_type just before the affected lines, or // ignore_for_file: strict_raw_type in the affected libraries. You could also pass dynamic as an actual type argument explicitly. So you do have a number of ways to enable this warning and still disable it in some locations.

base type of a map retrieval operation is Object?, and yet the dart SDK seems to be ignoring it in this specific scenario.

The return type of operator [] on Map<K, V> is V?. It will be dynamic? (which is immediately normalized to dynamic, which doesn't matter) when the actual type argument passed to V is dynamic.

So there's nothing exceptional about the type of map['hello'] in this case.

from sdk.

eernstg avatar eernstg commented on September 25, 2024

if it can cast it to Object? than it should be able to call an extension on Object? too

As I mentioned here, dynamic is assumed to have all instance members, with all possible signatures, and (even assumed) instance members have a higher priority than extension members.

The assumption of "has all members" is not a subtype based property, this is a special affordance which is given to the type dynamic, such that developers can choose (by having a receiver of type dynamic) to perform member invocations that are not statically type safe, and only check at run time that the given member actually exists and that it accepts the given kind of invocation (e.g., that it accepts that many parameters, of those types, etc.).

The consequence, as @lrhn mentioned here, is that

extension methods do not work on dynamic.

You will simply never be able to execute any extension methods when the static type of the receiver is dynamic.

It is true that dynamic is a subtype of Object? (and vice versa), but because of this special "it has all members" assumption it is not true that "it should be able to call an extension on Object?".

from sdk.

eernstg avatar eernstg commented on September 25, 2024

This is not documented behavior even.

If you want the gory details then check out the section 'The instantiation to bound algorithm' in the language specification. The choice of dynamic when there is no bound is found at the end of the first paragraph in that section.

from sdk.

eernstg avatar eernstg commented on September 25, 2024

We had this code in our codebase and it was working fine until now.

Nothing has changed about the fact that raw type annotations use instantiation to bound, and instantiation to bound uses dynamic when no bound is declared for the corresponding type variable. I cannot see how it could be possible that a receiver of type dynamic has an extension member invoked on it, nor how map[...] could have any other type than dynamic when map has type Map<dynamic, dynamic>. So I really don't know how that could work, with any version of Dart...

from sdk.

lrhn avatar lrhn commented on September 25, 2024

Agree. This seems to be working exactly as intended (or at least as currently specified).
Until we get rid of the currently specified behavior, there is no bug, and the only surprising thing here is that it used to work.

from sdk.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.