Skip to content
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

Inferred constructable types #47559

Closed
5 tasks done
brandon942 opened this issue Jan 22, 2022 · 15 comments
Closed
5 tasks done

Inferred constructable types #47559

brandon942 opened this issue Jan 22, 2022 · 15 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@brandon942
Copy link

brandon942 commented Jan 22, 2022

Suggestion

This issue proposes constructable inferred types. It solves this:

// Ability to construct objects without errors
var obj = {a:1} 
obj.b = 2 // Error: typescript presumes that obj is meant to be the finished type {a:1} - It isn't.
obj.c = 3 // Error

Proposed syntax which says "This is not a finished type. This is a type under construction.";

var obj : unfinished
var obj : constructable
var obj : inferred

Then:

var obj = {a:1} as unfinished
obj.b = 2
obj.c = 3 
obj // {a:1, b:2, c:3} - OK

var something1 : unfinished
something1 = 1
something1 = "a"
something1 // number | string

var something2 : unfinished
something2 = new myClass
something2.newProp = 1
something2 // myClass & {newProp: 1}

var something3 : unfinished = {a: 1}
something3.b = 2
something3 = "a" // Compile Error! Primitives and objects are incompatible ? - No.
something3 = "a" // Yes:  {a: 1, b: 2} | "a"
something3 // is the subtype of;    {a: number, b: number} | string
something3 // and should be assignable to any type that that has this form or subform

var something4 : unfinished = {a: 1}
something4 = new myClass
something4.newProp = 2
something4 = {c: 3}
something4 = [1]
something4 = "a"
something4 = false
something4 // [1] & {a: 1, c: 3, newProp: 2} & myClass | "a" | false
something4 // which is the subtype of:  Array<number> & {a: number, c: number, newProp: number} & myClass | string | boolean

// Inference should always work without errors. 
// This allows turning a ".js" file into a ".ts" file with 'fewer' issues if "unfinished" is the default for the file. 
// If "unfinished" is the default then the "unfinished" typehint is not needed. That must always be the case in "js" files.
// In a ".js" file:
function getValue(x) {
	var o = {}
	o.a = 1
	if(x == 2) o.b = 2
	else if(x == 3) o = "c"
	else o = x.else
	return o
}
var val = getValue(window.x)
val // {a: 1, b?: 2} | "c" | any

Basically, all object types are combined "&", all primitives are added as alternatives "|".
An inferred constructable type also means, and the "unfinished" typehilt also says: "The code defines the schema of the type".
So you don't need an interface or need to keep an interface that you have laying around somewhere in sync with the code that defines it. You can reference the type via type t = typeof theVariableThatHoldsIt.

Current Workaround:
Typescript already has the ability to infer a constructed type, but only with the identifier of a function statement and without narrowing. This can be expanded to all variables.

function obj() {}
obj.toJSON = ()=>Object.assign({}, obj) // if serialization is ever wanted - Problem: the "toJSON" property is added.
obj.a = 1
if(x) obj.b = 2
obj // function obj(): { (): void; toJSON(): ...; a: number; b: number; }

🔍 Search Terms

inferred types, constructable type, extendable object

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

-->

@xiBread
Copy link

xiBread commented Jan 22, 2022

You realize all of this can be solved by just… using the current type system how it's supposed to be used

@brandon942
Copy link
Author

@xiBread are you developing a competitor tool? Link it!

@xiBread
Copy link

xiBread commented Jan 22, 2022

?

@brandon942
Copy link
Author

I'm still learning the irks & quirks of typescript - and btw, I just figured out that type construction for objects is already implemented to some degree - but only within .js files! I have also figured out that you can import types into .js files, not just variables of a type.

import { YesIAmAType } from "./types"
/** @type {YesIAmAType} */
var myVariable

The import is dropped by the bundler. JSdocs offer additional ways to define types inside comments. I don't know about you guys but I'd rather have useful type annotations in comments than @ts-ignore all over the place.
So as it turns out, there is absolutely no reason at all to work with .ts files.
You lose out on nothing. You get all the productivity gains of api descriptions and auto completion that types are meant to provide - and you avoid all the productivity obstacles of typescript. Like #46941, #47560, this #47559, .... and countless other issues + dealing with workaround after workaround after workaround. You also avoid committing your codebase to a syntax that may change, - or exposing yourself to a situation where walking back or migrating to a different typing system in the future with different development tools becomes impossible.
As I've mentioned in #46941. A typing system is not supposed to "validate" an entire file and meddle with things that are outside of its area of responsibility. It is supposed to simply provide helpful hints where they are wanted. Nothing more. In that respect typescript's philosophy is utterly flawed.

@xiBread
Copy link

xiBread commented Jan 22, 2022

A typing system is not supposed to "validate" an entire file and meddle with things that are outside of its area of responsibility. It is supposed to simply provide helpful hints where they are wanted. Nothing more. In that respect typescript's philosophy is utterly flawed.

??? are you trolling

@Josh-Cena
Copy link
Contributor

If you just want helpful hints instead of validation, then JSDoc is the right fit for you!

@brandon942
Copy link
Author

brandon942 commented Jan 23, 2022

@xiBread what do you mean trolling? Most of my issues deal with reported errors that are none.
@Josh-Cena I said "validate" an entire file, and "validate" at that with wrong assumptions. I do want validation - useful validation - in signatures and object schemas - but for types of my choosing and in areas of my choosing. I neither need nor am I willing to put up with useless and worng validation, and I put that in quotes because that is no validation at all. And let me stress: A "type validator" you can be at most - for as long as the types are correctly assumed; a code validator you neither can nor will ever be for a dynamic language!

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript labels Jan 24, 2022
@RyanCavanaugh
Copy link
Member

See also #12416

For top-level types, this is just not writing a type annotation:

var something1;
something1 = 1
something1 = "a"

For adding properties onto existing objects, this is an intentional opt-out for TS; the complexity of doing this in all possible cases would be too much of a performance/complexity trade-off for what it allows you to do.

Anyway it does sound like JS Doc is a much better fit for what you want out of a tool.

@brandon942
Copy link
Author

@RyanCavanaugh That is equivalent to saying as any, which is how people go about it. Youre giving it an any type from the start and it doesn't matter what is done with it. Or are you saying the type that something1 ends up with is tracked in the top level? I can't reproduce that.

@brandon942
Copy link
Author

Btw, I just stumbled on ((you can even type function statements in .js files)) which delivers on ALL points in #46941. The more I look at it... the number of features available in .js files and not available in .ts just keeps on growing!

@RyanCavanaugh
Copy link
Member

That is equivalent to saying as any,

No, it isn't. You can assign into it any type, but it behaves as a non-any on any read operation, which is the behavior you're asking for.

var something1;
something1 = 1
something1 = "a"
// Error
something1.foo;

@brandon942
Copy link
Author

@RyanCavanaugh You probably should have noted that this is a noImplicitAny behavior. But as you have said it does not allow for a type override or object construction and will error on valid code like something1 = []. Once again it's focused on "validation" instead of correct type tracking. If you want validation well just say it something1 : beThisType.

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@matthew-dean
Copy link

matthew-dean commented Feb 7, 2024

@RyanCavanaugh Maybe you can clarify something from this thread. Philosophically, does the TypeScript team consider .js with "allowJs": true and "checkJs": true to not be TypeScript? Because my mental model is that this is TypeScript but just a different syntax. If it is TypeScript in terms of type inference, then this feature already exists but is "turned off" for .ts extensions, and the fact that some of TypeScript's type-inference features are removed for .ts sometimes seems a little backwards and frustrating.

@RyanCavanaugh
Copy link
Member

They're not intended to be identical. There are probably a dozen or so behaviors that are different between TS and JS, and this is on purpose to try to suit the general different needs of people who are and aren't using type annotations to indicate their intent.

Making those individually-controllable means expanding our configuration matrix by a factor of ~4,000, and opens the door to many more "bad" configuration spaces that people can find themselves in, plus the cost of documenting, reasoning about, and supporting all those possible configurations. It's not something we're particularly interested in doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants