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

Suggestion: Allow type annotations for const assertions #50157

Closed
5 tasks done
Not-Jayden opened this issue Aug 3, 2022 · 4 comments
Closed
5 tasks done

Suggestion: Allow type annotations for const assertions #50157

Not-Jayden opened this issue Aug 3, 2022 · 4 comments

Comments

@Not-Jayden
Copy link

Not-Jayden commented Aug 3, 2022

Suggestion

🔍 Search Terms

const assertions with type annotations, const assertions, type annotations, typed variables with also using as const

✅ 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.

⭐ Suggestion

I really love the power of using const assertions for creating deeply immutable and narrowly typed constants, although I feel they have a relatively major flaw in the fragility they introduce when you have a specific shape of data you want a variable to adhere to, as you ultimately have to go and perform the type-checking manually if you make any human mistakes.

This proposal is to allow us to get the best of both worlds, by introducing a new additional syntax to optionally allow for adding a type annotation to a const assertion, in the same manner you would add a type annotation at the start of a variable normally using a colon. e.g.

const ANIMALS = {...} as const: Record<Animal, AnimalInfo>;

The type checking would then effectively perform be performed on the value as it normally would to ensure that the value adheres to the type in the type annotation, and return any errors to be resolved if not.

Importantly however, the actual type of the variable would still remain the deep readonly/immutable type that is generated from using a const assertion.

📃 Motivating Example

Take the following example that the suggestion above references from, where we are trying to get the ANIMALS constant to follow the shape of Record<Animal, AnimalInfo>

enum Animal {
	CAT = 'cat',
	DOG = 'dog',
	MOUSE = 'mouse';
}

interface AnimalInfo {
	sound: string;
	lifeExpectancy: number;
}

const ANIMALS = {
	[Animal.CAT]: {
		sound: 'meow',
		lifeExpectancy: 15,
	},
	[Animal.DOG]: {
		sound: 'woof',
		lifeExpectancy: '10', // NOTE: the type here has accidentally been declared as a string instead of a number
		favoriteFood: 'bone' // NOTE: The AnimalInfo interface does not include a favoriteFood property
	}, // Note: the constant is missing an entry for Animal.MOUSE
} as const;

As you can notice from the embelished example above, it can be quite easy to make mistakes when using const assertions that would ordinarily get picked up as errors just by using a normal type annotation (e.g. const ANIMALS: Record<Animal, AnimaInfo>).

Even if you were to try and use both a normal type annotation and a const assertion like this for example:

const ANIMALS: Record<Animal, AnimalInfo> = {...} as const;

unfortunately (though I think it makes some amount of sense), only the type from the type annotation (Record<Animal, AnimalInfo>) is what will get cast for the type of the ANIMALS constant, effectively making the const assertion redundant.

By allowing the type annotation to happen after the const assertion, we can get the desired benefits without introducing any breaking changes.

💻 Use Cases

The general use cases for this have been for the most part covered by the motivating example above. Essentially you would want to use this anywhere where you want to have the benefit of both performing normal type checking for a variable's value against a type annotation to ensure a consistent shape for the value's data, while still having the type of that variable be cast to the narrower, deeply read-only type that is generated from a const assertion.

There aren't really any existing workarounds for this that I am aware of. The best alternative I've been doing personally is just manually adding the type annotation to a variable and removing it again whenever I want to make changes to it while ensuring I'm not breaking the shape of the data.

Any thoughts, feedbacks, suggestions, or criticisms on this propsal would be greatly appreciated🙏

@jcalz
Copy link
Contributor

jcalz commented Aug 3, 2022

Looks like the so-called satisfies operator as discussed in #7481 and now #47920.

@fatcerberus
Copy link

const ANIMALS: Readonly<Record<Animal, AnimalInfo>> = {...};

?

@Not-Jayden
Copy link
Author

const ANIMALS: Readonly<Record<Animal, AnimalInfo>> = {...};

?

That is still not the equivalent to the stricter type of what ANIMALS is actually being cast to by using a const assertion.

@Not-Jayden
Copy link
Author

Looks like the so-called satisfies operator as discussed in #7481 and now #47920.

@jcalz Yep you're right, good find. Will close this suggestion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants