Compiled .d.ts output for `Omit` is verbose and semantically inconsistent
See original GitHub issueTypeScript Version: 3.7.1-rc (but not appreciably different in 3.5.1)
Search Terms: Omit, Pick, verbose, long
Code
// HtmlAttrs.ts
export type InputAttrs = React.InputHTMLAttributes<HTMLInputElement>;
// TextFieldWidget.tsx
import { InputAttrs } from '../../utils/HtmlAttrs';
export interface TextFieldProps {
onChange?: (e: React.ChangeEvent<HTMLInputElement> & { isComposing: boolean }) => void;
}
export const TextFieldWidget = React.forwardRef<HTMLInputElement, TextFieldProps & Omit<InputAttrs, 'onChange'>>((props, ref) => {};
// Output in TextFieldWidget.d.ts
export declare const TextFieldWidget: React.ForwardRefExoticComponent<TextFieldProps & Pick<InputAttrs, "children" | "dir" | "form" | "slot" | "style" | "title" | "hidden" | "pattern" | "disabled" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "className" | "contentEditable" | "contextMenu" | "draggable" | "id" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "inputMode" | "is" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "size" | "multiple" | "list" | "step" | "autoFocus" | "type" | "height" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "name" | "value" | "width" | "alt" | "crossOrigin" | "src" | "checked" | "maxLength" | "readOnly" | "accept" | "autoComplete" | "capture" | "max" | "min" | "minLength" | "required"> & React.RefAttributes<HTMLInputElement>>;
Expected behavior:
I expect the .d.ts definition to mimic the structure of the original definition, i.e. to use Omit
rather than Pick
. In particular, this changes the self-documenting semantic of using Omit
. The original definition is intended to make it obvious that the signature of the onChange
event is not the standard definition from React, but an augmented type.
BTW – there are more TextFieldProps
in the real code. The point of defining them in a separate interface that doesn’t extend the React props, and then combining the types in the component definition, is to make it easy to document, browse, and create objects containing the component-scope props, without them getting lost in the noise of 260+ native HTML props.
Actual behavior:
Instead, the Omit
definition is replaced by the compile-time equivalent as a Pick
statement. This defeats the self-documenting semantic that is intended by using Omit
. It is very confusing to library consumers – “why is there such a gigantic Pick
type with 200+ entries???” And it actually changes the intended semantic, as the meaning of the Omit
-based type is intended to be evaluated by the typescript compiler when the user of the library compiles, not when the library itself is compiled. e.g. if a new version of @types/react
includes a new event type, I should not have to recompile/redistribute my library to support it.
I don’t-know-I-don’t-know WAY MORE about compilers than I know, so I am certainly open to education on this, but both of the issues raised here do seem like (subtle) bugs to me.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:7
- Comments:17 (10 by maintainers)
Top GitHub Comments
Or we can just have some way of telling the compiler to not expand a type alias, https://github.com/microsoft/TypeScript/issues/35654
@ExE-Boss I edit sniped you with a corrected declaration that does work.