Skip to content

Legacy code interaction with Records #2426

Closed
@lrhn

Description

@lrhn

tl;dr: I don't think there is any problem with this, it should just work. Writing this to give a place to write concerns, if we come up with some.

If we introduce records in Dart 2.x.0, existing pre-2.x.0 code will may up interacting with record types and record values.
(Some of it pre-2.12.0 code too.)

What we've done with type system changes so far (aka: the null safety update) is to have a shared type system that both legacy and new code interacts with, but the legacy code has none of the new syntaxes available.

Legacy code seeing record values or types.

Record values can flow into legacy code typed as Object.

Opaque record types can flow into legacy code as type parameters, or type arguments of instances of generic classes (like <(int, int)> or List<(int, int)>. The static type of those in the legacy code will likely not contain record type.

Concrete record types can be introduced in legacy code by referencing type aliases from new code, or by having record types be filled in by type inference for legacy code referencing new code. (At the extreme, a legacy class extending a new-code class can inherit the parameters and return type of a method, which can contain record types.)

All of these should probably just work.

Since we make Record a subtype of Object, the legacy code won't need to do anything special, just treat the record types and objects as types and instances of types declared in other libraries that it hasn't seen.
(It actually has seen at least one of them, since Record will be in dart:core, unless we hide that declaration from pre 2.x.0 code. We probably don't need to hide it.)

There probably shouldn't be any problems with that. Legacy code treats the record types as any other foreign type.

Member access

I have suggested that member access on records could be "extension getter-like" so that it doesn't work with dynamic invocations. (Also means you cannot pass (x: 1, y: 2) to something which expects a Point, but does dynamic member access instead of typed member access.)

If we do that (EDIT: We don't! Members are normal instance getters), a legacy library can only destructure a record using typed access to field getters, r.$0 and r.fieldName, since it cannot use pattern matching, cannot cast the record to a static record type (unless it uses a type alias from another library), and cannot access field getters, even through dynamic invocations.

If member access uses normal virtual methods, a dynamic invocation of (o as dynamic).$0 or (o as dynamic).fieldName would also be able to destructure the record.

Legacy code deliberately using record types, by using type aliases from new-code libraries, is probably going to be rare. Might as well just migrate. If the code is pre-2.12 too, you should really prioritize null safety migration over using records.

I don't think legacy code not being able to interact with records, other than by shuffling them around, is a problem. Might even be seen as an advantage, and any legacy code that actually works on concrete record types may be worth warning about.
(At least upgrade the SDK min requirement, and add a //@dart=2.x-1 marker, since the code definitely depends on a 2.x.0 SDK.)

Metadata

Metadata

Assignees

Labels

recordsIssues related to records.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions