ballerina-platform / ballerina-spec

Ballerina Language and Platform Specifications
Other
167 stars 54 forks source link

Optional field access with union type #496

Open KavinduZoysa opened 4 years ago

KavinduZoysa commented 4 years ago

Description:

import ballerina/io;

type Foo record {
    int i;
};

type Bar record {
    string s;
};

public function main() {
    Foo f = {i:23};

    Foo|Bar fb = f;
    io:println(fb?.s);
}

Currently the type of expression fb.s is determined as string. Shouldn't that be anydata?

jclark commented 4 years ago

It's an interesting example.

The type should clearly be anydata|string which is the same as anydata.

But this example makes me think that we shouldn't allow E?.x when the static type of E is a union, unless x is an individual field in all branches of the union.

Where the spec now says:

(if the type descriptor is a union, then this requirement must be satisfied by at least one member of the union)

I am thinking it ought instead to say:

(if the type descriptor is a union, then this requirement must be satisfied by all members of the union)

@hasithaa @sameerajayasoma Any thoughts?

sanjiva commented 4 years ago

Doesn't that kind of defeat the purpose of union for optional fields?

MaryamZi commented 4 years ago

Now that we have never, what if the requirement is something along the lines of

(if the type descriptor is a union, then this requirement must be satisfied by at least one member of the union, and the rest of the members can never have a field-name field)

So, optional field access will still work for

type Foo record {
    int i;
};

type Bar record {|
    string s;
|};

public function main() {
    Foo|Bar fb = {i: 23};
    int? i = fb?.i;
}

and

type Foo record {
    int i;
};

type Bar record {
    string s;
    never i?;
};

public function main() {
    Foo|Bar fb = {i: 23};
    int? i = fb?.i;
}

But in other scenarios, such as the following, you'll have to use member-access?

type Foo record {
    int i;
};

type Bar record {
    string s;
};

public function main() {
    Foo|Bar fb = {i: 23};
    anydata i = fb["i"];
}
type Foo record {
    int i;
};

type Bar record {|
    string s;
    int...;
|};

public function main() {
    Foo|Bar fb = {i: 23};
    int? i = fb["i"]; // Will be `int?`.
}
jclark commented 4 years ago

There are four possibilities for each branch of the union for an expression E.s

  1. required individual field e.g. record { string s; }
  2. optional individual field e.g. record { string s?; }
  3. not allowed as a field e.g. record {| int x; |}
  4. allowed as a non-individual mapping member e.g. map<string> or record { int x; }

In the case above, we have one branch is case 1 and one branch is case 4: that seems to me really weird, not useful and most likely to be an error.

Above, I suggested that they requirement should be that all branches are case 1 or case 2. But I think that's too restrictive, since it would disallow a combination of case 3 with case 1 or 2.

So a better restriction (this is for the non-lax case):

With this restriction, if E?.s is allowed, then there are two runtime possibilities:

Note that we don't allow E.s when E has static type of case 4.