ballerina-platform / ballerina-spec

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

Handle intersections for mapping/list constructors and cloneWithType #1143

Open jclark opened 2 years ago

jclark commented 2 years ago

Currently the spec handles unions in the contextually expected type for mapping and list constructors, but not intersections.

The cloneWithType/fromJsonWithType functions work similarly to constructors and should also be able to handle intersections. This can happen when using Open API with a JSON schema that uses allOf.

One important special case is intersection with readonly.

This is a precondition to #544.

Edited: #544 will deal just with case of intersection with readonly

jclark commented 2 years ago

What makes this difficult is that this cannot be specified in terms of pure types (sets of shapes), since we need to apply default values which are not included in the type. The spec currently describes this in terms of the type descriptor and deals with just unions (by eliminating those members of the union that are not applicable).

But this is not really sufficient: we need to do some amount of processing on the type descriptor (for example to resolve references). We also need to select out the relevant basic type from the type descriptor (the spec does this currently with the concept of an applicable basic type).

jclark commented 2 years ago

The simplest way to do this is to specify a normalized form of the applicable contextually expected type. For a contextually expected type T, the applicable contextually expected type means T & B where B is (any|error)[] for list constructors and map<any|error> for mapping constructors. The normalized form is a union, where each member of the union is either an atomic type descriptor A or an intersectionreadonly & A`. An atomic type descriptor is a tuple type descriptor in the list case or a record type descriptor in the mapping case.

This normalization process is applied to a syntax tree. I will describe it for the mapping case. It can be broken down into the following steps.

  1. Recursively expand type descriptor references (including json, any, anydata), stopping when you get to
    • a record type descriptor; in this case, leave as is
    • a map type descriptor; in this case, replace by the corresponding record type descriptor
    • a descriptor that does not contain any shapes of basic type mapping; in this case, replace by never
    • a reference to a constant of type mapping (not implemented in nBallerina or jBallerina currently)
    • a readonly type descriptor; in this case, leave as is
  2. This leaves a type descriptor consisting of unions, intersections, record type descriptors, constants, readonly, never. Now, get rid of never by using the identities T|never == T and T&never == never. If the whole descriptor is never, then that is treated as union with no members.
  3. Now transform to a union of intersections, by distributing intersections over unions e.g. T1 & (T2|T3) becomes (T1 & T2)|(T1 & T3). This leaves a union, where each member is an intersection whose members are readonly, record type descriptors or constants.
  4. Now transform each member of the union into the required form, by transforming an intersection of two record type descriptors R1 & R2 into a single record type descriptor R: R will have an individual field descriptor for every field name f that occurring in R1 or R2; the type of the field is the intersection of the types from R1 and R2. If this produces a required field with type never, then the record type descriptor is replaced by never and never is removed as in step 2. XXX need to handle constants here

544 specifies how to deal with defaults in step 4.

jclark commented 2 years ago

There's another subtlety: it need not be an error if we end up with more than one alternative if all of them are readonly, since in the readonly case we do not need an inherent type.

jclark commented 2 years ago

In #544 we will handle just intersection with readonly. This issue will handle defaults in the general case as described here https://github.com/ballerina-platform/ballerina-spec/issues/544#issuecomment-1199267849

jclark commented 1 year ago

Also need to handle annotations (as in #1258).