ballerina-platform / ballerina-spec

Ballerina Language and Platform Specifications
Other
168 stars 53 forks source link

Can we improve type narrowing for mutable basic types? #908

Open jclark opened 3 years ago

jclark commented 3 years ago

For the applicable type, I think there are also some cases where we can use a narrowed type, even in non-readonly cases.

For example, can't the applicable type at the start of the second clause of the following match statement be Marks?

type Employee record {|
    string name;
    int id;
|};

type Marks record {|
    int math;
    int physics;
    int chemistry;
|};

function func(Employee|Marks rec) {
    match rec {
        var {name, ...rest} => { // (1)

        }
        var {...rest} => { // (2)
            map<int> mn = rest;
        }
    }
}

Originally posted by @MaryamZi in https://github.com/ballerina-platform/ballerina-spec/issues/827#issuecomment-828204811

jclark commented 3 years ago

In this case we know that if a value belongs to type Employee|Marks, then it either belongs to Employee or it belongs to Marks.

This is not something that we know in general. In terms of sets if R is the union of E and M, and S is a subset of R, then it possible for S to be a subset of neither E nor M.

In this case we know it, because we know that

Our normal type narrowing algorithm works by creating an "immutable partition" of a type into uniform types. It's an immutable partition in that if a current shape of a value v is in one subset of the partition, v cannot be mutated so that it's shape is in another subset of the partition.

So the question then is whether we can in a general away perform a similar "immutable partition" of a subtype of a mutable uniform basic type. If we can, then this is applicable to if/else, as well as match.

For this to be really useful, this should handle the case where we have a readonly field that is being used like a nominal type tag.

// Note that records are now open.
type Employee record {
    readonly "Employee" tag = "Employee";
    string name;
    int id;
};

type Marks record {
    readonly "Marks" tag = "Marks";
    int math;
    int physics;
    int chemistry;
};

function func(Employee|Marks rec) {
    match rec {
        { tag: "Marks" } => {  }
       _ => { 
           Employee e = rec; // should be OK
        }
    }
}