-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
Authors often want to specify that a parameter may be passed any string, but has a set of preferred values. Right now, Typescript has no concept of 'preferred type', even though people try to indicate by unioning it with the declared type: string | 'a' | 'b' | 'c'. However, the compiler reduces this to string early on. People have used workarounds to fool the subtype reduction machinery, like string & {} | 'a', but PRs like #49119 improve reduction and unintentionally break these workarounds from time to time.
Based on discussion from the May 20 2022 Design Meeting and suggestions from Discord [1] I propose an explicit jsdoc tag to specify the preferred type:
/**
* @suggest {'foo' | 'bar'} e
*/
function f(e: string) {
}This will instruct the language service to offer completions for e that wouldn't be possible given just its declared type. Documentation generators can also display the preferred type along with the declared type.
The syntax is similar to @param:
@suggest { type } identifier
But it can be used with any declaration:
/**
* @suggest {'foo' | 'bar'} T
*/
function f<T>(p: T): T {
}
/** @suggest {'a' | 'b'} x */
declare var x: string
interface I {
/** @suggest {'a' | 'b'} p */
p: string
}
const o: I = {
/**
* @suggest {'a' | 'b' | 'c'} p Suggestions can conflict
*/
p: 'a'
}
declare class C {
/**
* @suggest {HTMLAnchorElement | HTMLDivElement} p
*/
p: HTMLElement
}In Javascript, usage is more redundant:
/**
* @param {string} p
* @suggest {'a' | 'b'} p
*/
function f(p) {
}In Typescript and checkJs files, the compiler should issue an error if the suggested type is not a subtype of the declared type:
/**
* @suggest {'a' | 'b' | 0} p
// ^^^^^^^^^^^^^ Suggested type must be a subtype of the declared type
*/
function f(p: string) {
}And if the suggested type is never, then nothing should be added to the suggestion list. Maybe documentation generators should treat this as a signal that the property is deprecated (although -- why not use @deprecated in that case?).
Other options:
- Do nothing. Make sure at least one workaround remains to make the compiler leave reducible unions unreduced.
- Directly support
string | 'a' | 'b' | 'c': have the compiler create a new string type for every string used in a literal union. Proposed in Support Intellisense for string/number literals in a widened union #33471.
Other design questions
- Should the tag specify values instead of types?
This seems clunky for the common case, a list of values:
/**
* @suggest {['a', 'b', 'c']} p
*/- Could the tag be simplified by allowing it only after another tag for a declaration?
The syntax could be simplified to @suggest { type } in that case. That is,
/**
* @param p @suggest {'a' | 'b'}
*/But this is awkward in Typescript where people aren't used to writing @template for type parameters, for example. And few other JSDoc tags are context-sensitive like this -- @typedef/@property is the main one, and it's notoriously hard to use.
- Should the suggestion be integrated on the end of existing tags?
That is,
/**
* @param {string} p {'a' | 'b'}
*/I can't think of a good syntax for this.
- This is the first tag that TS users would be using to specify types in JSDoc. Is this OK?
Tag name
The tag is intended to specify a subtype of the declared type. Tools besides the compiler will use the type as an auxiliary to the declared type. Here are a few ideas for names:
@prefer@suggest@suggest-type@suggesttype@suggestType@intended@expected
[1] Thanks to cspotcode, @Gerrit0, Andrew Kay, d-fischer and Elijah from #language-design.