Add a poor man's version of branded types, and lint inconsistencies that are otherwise invisible to the type system #57828
Labels
devexp-linter
Issues with the analyzer's support for the linter package
legacy-area-analyzer
Use area-devexp instead.
linter-lint-request
type-enhancement
A request for a change that isn't a bug
This is a proposal for a lint that flags inconsistent usages of a sort of branded types.
A bit of motivation first: One well-known source of bugs is the use of the same type for different purposes, which makes it possible to pass arguments with one meaning in the application domain to formal parameters with a different meaning. Here is an example:
In this example, it's very likely to be a bug that we pass the actual argument
weight
to the formal parameter namedage
offeedPerson
and vice versa, but the type system can't see any problems because they are both just of typeint
.Branded types are all about being able to create different "copies" of any given type, such that we can detect this kind of problem: A branded type
T1
is a copy of an existing typeT
which is considered to be different from another branded typeT2
whenT2
was created from a different type thanT
, but also whenT2
was created fromT
. For instance, we could create branded typesWeight
andAge
fromint
, and we would then be able to flag inconsistent data flows:Pascal has had declarations like
TYPE Weight = Integer;
since the 1970'ties, and Haskell hasnewtype
which enables a simple (one-case) algebraic type to use erasure (such that we get a new type, but the run-time representation is the same as the underlying type). So we could definitely do a similar thing in Dart.However, I do not believe that we will do this: It's a non-trivial subsystem in the type system that we'd need to add, and it is connected to a huge domain of specialized type system features that we probably don't want to promise that we'll add to the language.
Hence, this issue is aimed at doing a similar thing in terms of lints alone, with no changes to the Dart language or its type system.
We could do "manual branding" of type annotations based on metadata:
When
BrandedType
is available, we could use it as follows:We could then have a lint that knows about
BrandedType
and checks actual arguments against formal parameters, flagging all occurrences where a value whose static type has one brand is passed to a formal parameter with a different brand (that would be in addition to normal type checks, of course). This would allow us to get the two lint messages above at the commentLinted twice!
.Apart from simple data flow restrictions, there could be arbitrary lints for derived checks. For instance, we would want to allow literals to be assigned to branded types, we might want assignments from a branded
int
source to a plainint
target or vice versa to be accepted or rejected, we might want the product of twolengthDouble
values to have brandareaDouble
(cf. F# units of measure), etc.etc.There is no end to the wealth of features that people have invented in this area, which is a good reason for handling it with lints, rather than making all these things part of the language.
One particular case that doesn't really call for a lot of magic is that of function types, cf. #57827. It is my impression that the underlying problem in that issue is that we have no way to brand function types.
In particular, using an example which is similar to the one mentioned above, we get the following:
BrandedType
should not be a subtype ofType
(they are used differently), but a developer could make the choice to useBrandedType
instances as a near replacement forType
in some idioms:The text was updated successfully, but these errors were encountered: