microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.65k stars 12.44k forks source link

explicit type assignment modifier keyword for function/constructor parameters typically #25053

Closed wesleyolis closed 2 years ago

wesleyolis commented 6 years ago

The problem of trying to communicate the input of a function, which needs to be able to take a type any or typically a specific type structure. What we are looking to communicate is that this function takes a specific type or potentially some json type from disk, however, using any type, relaxes all the constraints, which defeats what we are wanting to achieve.

AnyType, means we are not prepared to make an assumption about the type, we not for any reasons, prepared to further constrain the type. We can only know the constrained type for absolute certainty after an Any type has been validated at run time to be of a certain type form against which, we were validating, it can then after validation be assumed the type against which we have validated.

Before validation, the function must take in an json(any) object read from disk or something or if it is to take in a validate type, because when were testing then we like it to be of some specific type.

const schema ={} // implicit type of SchemaDef
type Schema = ExtractSchema<typeof schema>;
function validate(object : any | Schema, schema : SchemaDef ) : Schema {}

However, this basically allows this function to take anything, which not really what we are wanting. We want it to take either any or a Schema type explicitly and nothing else.

Feature: add key word explicit before type assignment parameter, cause this mode the describe behaviour above.


const schema ={} // implicit type of SchemaDef
type Schema = ExtractSchema<typeof schema>;
function validate(object : explicit  any | Schema, schema : SchemaDef ) : Schema {}

or we need to change how the union operator works, with any | Schema should be interpreted, but need ensure it doesn't create confusion between union between sets. I feel it would be better to rather go for consistence with idea of how sets and unions now work and should work, were Any consume and nukes others types, for reason in post mentioned below, that feel their is light at the end of the tunnel on the direction can that needs to be taken. Introduce the explicit keyword is simpler than multiple function signatures or multiple function wrappers, which too verbose, alot of waisted key strokes. See the following for background on Any consuming and nuke other types

When looking at union and intersection with sets, we need ensure the the definition of when something is operated on like a sets and when it is and operations on merging two items. It also needs to make sense with introduction of this new explicit keyword as well and not render it irrelevant

Example of how the explicit type key word changes what types may be assigned.

interface A {}
interface B {}
interface C {}

function ExplictTypes(value : explict any | B){}
// 

const value = {a: 6, b : 8}; // this is an inferred type, and can be consider, and explicit Any
ExplictTypes(value) // Ok

const anyValue: any = {a: 6, b : "sdsf"};
ExplictTypes(anyValue) // Ok

ExplictTypes({a:4,b:6}) // Ok
ExplictTypes(4 as any) // Ok
ExplictTypes(3 as B) // OK  
ExplictTypes(3 as A) // FAIL
ExplictTypes(3 as C) // FAIL

const valueCast : B = value as B;
ExplictTypes(valueCast) // FAIL, casted types are not support for assignment in explicit mode.

ExplictTypes(<B>(3 as C)) // FAIL
ExplictTypes(<B>(3 as C)) // FAIL

const schema = {a : 34, B :'sdfsf'}  // typically this is more complex joi schema.
type Schema = ExtractTsTypeFromSchema<typeof schema>

function validateSchema( value : any | Schema, joiXSchema : JoiX.XSchema) : Schema
{
 return validate(value, joiXSchema);
}

const iSchemaInffered = {
a: 43345, B'sdfsdf'
}
validateSchema(iSchemaInffered,schema) //ok

const iSchema : Schema = {
a: 343, B :'sdf'
}

validateSchema(iSchema, schema) // ok
validateSchema(anyValue, schema) // ok
validateSchema({a:4,b:6} as C, schema) // fail
validateSchema({a:4,b:6} as B, schema) // fail
validateSchema({a:4,b:6} as A, schema) // fail
const obj = {a:4,b:6};
validateSchema( obj as A, schema) // fail
RyanCavanaugh commented 6 years ago

You can do this today:

var x: any;
function fn(x: { toString: never } | boolean) {

}

fn(""); // error
fn(32); // error
fn({}); // error
fn(true); // OK
fn(x); // OK
crdrost commented 6 years ago

Just to give a bit of broader perspective as someone who has written several validators, generally speaking you want a signature that looks like:

type Errable<T> = {errors: Array<path: string[], error: string>} | {value: T};
function validate<T>(
  object: any,
  schema: string,
  schemaLib: {[key: string]: MySchema},
  path?: string[] = []
): Errable<T>
) {/* validates `object` against `schema`, producing a normalized `T` */ }

By default this provides absolutely no connection between the T and the schema, we might say that T is a "phantom type".

TypeScript actually allows something much more powerful right now, which is that if you are very careful to get TypeScript to infer the most specific types possible for various things, you can actually write a runtime description of an algebraic data type, which you can use for runtime validation, for which TypeScript can infer the shape of values of that type. So you can have const mySchema = /* some composition of your functions */ and then you can define ValueOf<s extends Schema> such that you can then define, without the type parameter,

function validate<s extends Schema>(
  value: any,
  schema: s,
  path: string[] = []
): Errable<ValueOf<s>>

with the key being that you never explicitly declare what we called T: its runtime construction is precisely typed by TS which can then infer from this precise typing exactly what values of the form ValueOf<s> must be.

Inside either of these, of course, you are constructing a value of type any and then casting it at the last minute to the value of type T. It is very difficult to validate the validator except with a big test suite. But once you have the validator it makes everything else ten times easier.

wesleyolis commented 6 years ago

@RyanCavanaugh As long as you nest the item of interest in object with a key, then one should be able to do what you are saying, not then you have to never the every other interface and class in the word. So in this case explicit would be simpler than nevering the whole world on possibilities you don't want.

@crdrost The key thing we looking to do is extract a typescript ts and structure from schema instance definition, using typeof, which is what we have achieved and is working well, now. I update my post above, with a bit more context.. In the case of your example, you see were have inferable type, which we can constraint, schemaLib with.. we also have the ability to extract potential typescript type schema to be used in code for when something has been validated, so you get type validation and suggestion when writing code. The key here is that we would like for testing purposes, to be able to in places pass sections of valid configuration into process for validation and testing, starting with the Schema helps.. if want break things, change it to and any type, but at least ts engine is helping you write you code.

wesleyolis commented 6 years ago

In a similar manor to the above in creating a distinction between any and explicit any, it would be great to be able to differentiate between any, (any | undefined), (any | null) using the explicit key word. As I am find their any many cases, were I would like to differentiate between those 3 cases, so that I can by design know whether to check for possible of variable being undefined or nullable, or weather it would always be initialised to some form of structured JS type. Unfortunately I have found the typing system not able to meet some of my needs, so I have to take in specific types by functions, operator on the internal to the function as any and then cast them to the correct striped type after I have finished, otherwise things get too involved. But would be good when doing the internal work to be able to differentiate between any, undefined, null.