-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TypeSpec Language] object, tuple values and the value world #2046
Comments
valueof
objects
Design meeting feedbackNeed a way to resolve the type in the case of a union
function findTypeForValue(value: ArrayValue): ModelArrayType | Tuple;
function findTypeForValue(value: ObjectValue): Model;
function findTypeForValue(value: ScalarValue): Scalar;
function findTypeForValue(value: ScalarValue): Scalar;
function findTypeForValue(value: EnumMemberValue): Enum;
function findTypeForValue(value: Value): Type; |
Value entities
|
@timotheeguerin Please add in a Story Points estimate |
resolves #2046 [Playround](https://cadlplayground.z22.web.core.windows.net/prs/3022/) Add the new syntax for object literals using `#{`. For this first version an object literal can only contain other object literal and other literals(string, number, boolean)) ## Values axioms 1. `alias` always produces a type. If you attempt to alias a value, you get an error. 2. A string template produces a string template type if all substitutions are types, and a value if all substitutions are numeric, boolean, or string values. A mixture of types and values is an error. 3. The string literal syntax always results in a string literal type 4. A string literal type may be passed as a string value when the signature expects a value. When the signature expects either a string literal type or a string value, it is passed as a string value. 5. A string template type can be passed as a string value when all its substitutions are string literal types. ## Breaking change ### Removal of the `ValueType` replacement with `MixedConstraint` This shouldn't affect anyone as you were only exposed to this if you digged into the template parameter and looked at the constraint ## Deprecation ## Using a tuple instead of a tuple literal - ✅ still work - emit a warning <img width="1013" alt="image" src="https://github.com/microsoft/typespec/assets/1031227/ab05359a-5ed9-4a27-a8d1-f40d1e21766f"> - provide a codefix <img width="312" alt="image" src="https://github.com/microsoft/typespec/assets/1031227/5ef93bdf-665f-4445-a6b2-62475efe8c16"> ## Using a model expression instead of an object literal This technically didn't work before(different from above where tuple was used as a value) but allow this will allow us to convert most of our decorators to use `valueof` without being breaking ![Kapture 2024-03-18 at 19 31 32](https://github.com/microsoft/typespec/assets/1031227/f6d69ab4-139e-4b01-95a3-f376b8515d1c) ## Old decorator marshalling If a library had a decorator with `valueof` one of those types `numeric`, `int64`, `uint64`, `integer`, `float`, `decimal`, `decimal128`, `null` it used to marshall those as JS `number` and `NullType` for `null`. With the introduction of values we have a new marshalling logic which will marshall those numeric types as `Numeric` and the others will remain numbers. `null` will also get marshalled as `null`. For now this is an opt-in behavior with a warning on decorators not opt-in having a parameter with a constraint from the list above. Example: ``` extern dec multipleOf(target: numeric | Reflection.ModelProperty, value: valueof numeric); ``` Will now emit a deprecated warning because `value` is of type `valueof string` which would marshall to `Numeric` under the new logic but as `number` previously. To opt-in you can add the following to your library ```ts export const $flags = defineModuleFlags({ decoratorArgMarshalling: "value", }); ``` --------- Co-authored-by: Brian Terlson <brian.terlson@microsoft.com> Co-authored-by: Mark Cowlishaw <markcowl@microsoft.com>
This change enables three important features for the language
Describe the breaking change Back-compat design to avoid immediate breaking Impact
Targeted Sprint Additional information |
Values in TypeSpec
Problem
Currently we have a very limited set of types in TypeSpec that are used as values(
String
,Number
,Boolean
) as well as a misuse of other types(Model
andTuple
) in order to fill that gapFor example we might see this in the OpenAPI library:
which is then used like this
however previous signature allows other types to be passed as well, for example a union which is in this case not at all what we want
Current state
We currently have a way to request a value instead of a type using
valueof
keyword. This will automatically cast the type to the JS value in a decoratorvalueof
is limited tostring
,numeric
andboolean
scalars todayThis introduce the concept of values in the language however as it was limited to
string
,numeric
andboolean
scalars the existingString
,Number
andBoolean
types were still used to represent values depending on the context. Those places are:Approved design
A follow up part of the
valueof
proposal was to add object literals#{}
and tuple literals#[]
to fill the gap of the missing types.The design allowed property values to be types as well as values.
For example
Changes proposed
Distinction of values and types
As shown before the current state reuse the same types for values and types. However with the introduction of object and tuple literal we are now having some new entities that are only meant to be used as values.
Namespace
Model
ModelProperty
Union
UnionVariant
Interface
Operation
Scalar
Tuple
Enum
EnumMember
StringLiteral
NumberLiteral
BooleanLiteral
ObjectLiteral
TupleLiteral
null
unknown
Object and Tuple literals only accept other values
In order to make sure that types and value stay separated we need to make sure that object and tuple literals only accept other values as their properties/values.
This mean this example would produce an error
Contexts
There is 3 context that can exists in TypeSpec:
valueof
What does this mean?
valueof
valueof
x | valueof y
(e.g.unknown | valueof unknown
to allow any type or any value, or{} | valueof {}
to any type assignable to{}
or any object value)Value entities
const
statementsIn order to separate values from types we need to have a new equivalent to
alias
(meant for types) for values. This is whereconst
comes in.Syntax:
Example:
Evaluation:
const
like alias are evaluated in the reference order not in the declaration order.This means for example the following is valid:
This is needed as we are merging namespace, passing values to decorators which are not evaluated in the declaration order and this would cause all kinda of issues.
Scalar constructors
For primitives scalars(numeric, string, boolean) we can infer the type from a literal in most cases but for other scalars we can't know how to instantiate them, or sometimes it is ambiguous which primitive scalar a literal should be.
Primitive scalar constructors
This only apply to scalars that extends
numeric
,string
orboolean
(Not sure we need this one)As for those scalars are the base of every other values we do have to treat them specially. This mean they have a default constructor that will take their value.
string
for string scalarsNumeric
for numeric scalarsboolean
for boolean scalarsScalars named constructors
For other scalars as we cannot define a syntax to create every possible scalar we can instead allow scalars to define constructor in their body which can then be used to instantiate that scalar with the given values.
The constructor can be defined with the
init
(ornew
open for either) keyword.Those constructors do not actually instantiate the scalar with those values behind the scene they more keep reference of the parameters given to it so an emitter can convert that constructor into their coresponding language scalar initialization.
For example the
utcDateTime
scalar could look something like thatAnd could then be used like this
Type inference
Scalars
Whenever a value type is declared it is resolved against the constraint to see what type it is.
Single type case
Multiple type non-ambiguous case
Multiple type ambiguous case
When a value is assigned to multiple type and it is not clear which type it should be, it is a compile error.
Implicit type
If the target has no type then we just keep it as a XLiteralValue and keep the precise type. It might get assigned a scalar type next time it reach a constraint.
Models
As models are structural in the type there is never a defined model assigned to an object literal. It takes the format of whatever is its current assigned type.
For example given the following models
Scalars in models
In the same way as scalar works, scalars in models will need to resolve what type they are. Either by being explicit or implicitly resolving from the property scalar type. This also mean that if a property has ambiguous scalars then it will be an error
Internals
Things not included in this proposal
Accessing object or tuple literal properties
Things like this are not part of this proposal as it would involve designing optionality in values which doesn't seem necessary at this time and add more complexity to this already large proposal.
How does this affect users
Library authors
There is only 3 locations where values can get exposed to a library author
valueof
this would now also make it that we receive the JS object/array in the case of object and tuple values and null forNullValue
. ForEnumMemberValue
user would still receive it as that type as there is no equivalent.Value
type defined above but this would be a breaking change. We can however add a newdefaultValue: Value
property and deprecate the existingdefault
property(which wouldn't be set in the case of object and array values but keep working as before in the other cases).Spec authors
For spec authors the breaking changes are minimal, the only issue is that we used to allow tuples to be used as default values. However now those should be done with tuple literals.
To make this change non breaking we can allow with a deprecation warning tuple and models to be automatically casted to array and object values. For object this is not covering anything we supported before however allowing that would allow our libraries to migrate to using
valueof
which would then show a deprecated warning to the user which can be automatically resolved by a codefix.The text was updated successfully, but these errors were encountered: