fix: too complex type error when using union props type by handling

unions in binding generic
pull/17348/head
Richard Jelínek 8 months ago
parent 866ae6cdc7
commit 59e6d4fb98
No known key found for this signature in database

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix too complex type error when using union props type by

@ -1,5 +1,4 @@
import type { SourceMap } from 'magic-string';
import type { Binding } from '../phases/scope.js';
import type { AST, Namespace } from './template.js';
import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js';
import type { StateCreationRuneName } from '../../utils.js';

@ -116,6 +116,10 @@ type Branded<T, B> = T & Brand<B>;
*/
export type ComponentInternals = Branded<{}, 'ComponentInternals'>;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never;
/**
* Can be used to create strongly typed Svelte components.
*
@ -141,7 +145,7 @@ export type ComponentInternals = Branded<{}, 'ComponentInternals'>;
export interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> = {},
Bindings extends keyof Props | '' = string
Bindings extends keyof UnionToIntersection<Required<Props>> | (string & {}) | '' = string
> {
/**
* @param internal An internal object used by Svelte. Do not use or modify.

@ -56,7 +56,7 @@ export function element(renderer, tag, attributes_fn = noop, children_fn = noop)
* Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app.
* @template {Record<string, any>} Props
* @param {Component<Props> | ComponentType<SvelteComponent<Props>>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} [options]
* @param {{ props?: NoInfer<Props>; context?: Map<any, any>; idPrefix?: string }} [options]
* @returns {RenderOutput}
*/
export function render(component, options = {}) {

@ -376,7 +376,7 @@ export class Renderer {
* Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app.
* @template {Record<string, any>} Props
* @param {Component<Props>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} [options]
* @param {{ props?: NoInfer<Props>; context?: Map<any, any>; idPrefix?: string }} [options]
* @returns {RenderOutput}
*/
static render(component, options = {}) {
@ -493,7 +493,7 @@ export class Renderer {
*
* @template {Record<string, any>} Props
* @param {Component<Props>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options
* @param {{ props?: NoInfer<Props>; context?: Map<any, any>; idPrefix?: string }} options
* @returns {AccumulatedContent}
*/
static #render(component, options) {
@ -514,7 +514,7 @@ export class Renderer {
*
* @template {Record<string, any>} Props
* @param {Component<Props>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options
* @param {{ props?: NoInfer<Props>; context?: Map<any, any>; idPrefix?: string }} options
* @returns {Promise<AccumulatedContent>}
*/
static async #render_async(component, options) {
@ -592,7 +592,7 @@ export class Renderer {
* @template {Record<string, any>} Props
* @param {'sync' | 'async'} mode
* @param {import('svelte').Component<Props>} component
* @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options
* @param {{ props?: NoInfer<Props>; context?: Map<any, any>; idPrefix?: string }} options
* @returns {Renderer}
*/
static #open_render(mode, component, options) {

@ -13,7 +13,7 @@ export function render<
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: {
props?: Omit<Props, '$$slots' | '$$events'>;
props?: NoInfer<Props>;
context?: Map<any, any>;
idPrefix?: string;
}
@ -21,7 +21,7 @@ export function render<
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: {
props: Omit<Props, '$$slots' | '$$events'>;
props: NoInfer<Props>;
context?: Map<any, any>;
idPrefix?: string;
}

@ -10,6 +10,7 @@ import {
type ComponentInternals
} from 'svelte';
import { render } from 'svelte/server';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from '../../elements';
SvelteComponent.element === HTMLElement;
@ -337,6 +338,154 @@ render(functionComponent, {
}
});
// --------------------------------------------------------------------------- union props
const unionComponent: Component<
{
binding: boolean;
} & (
| (HTMLButtonAttributes & {
readonly: string;
})
| (HTMLAnchorAttributes & {
readonly2: string;
})
),
{ foo: 'bar' },
'binding'
> = (a, props) => {
props.binding === true;
if ('readonly' in props) {
props.readonly === 'foo';
// @ts-expect-error
props.readonly = true;
}
if ('readonly2' in props) {
props.readonly2 === 'foo';
// @ts-expect-error
props.readonly2 = true;
}
// @ts-expect-error
props.binding = '';
return {
foo: 'bar'
};
};
unionComponent.element === HTMLElement;
const unionBindingIsOkayToWiden: Component<any> = unionComponent;
unionComponent(null as any, {
binding: true,
// @ts-expect-error
readonly: true
});
const unionComponentInstance = unionComponent(null as any, {
binding: true,
readonly: 'foo',
// @ts-expect-error
x: ''
});
unionComponentInstance.foo === 'bar';
// @ts-expect-error
unionComponentInstance.foo = 'foo';
const unionComponentProps: ComponentProps<typeof unionComponent> = {
binding: true,
readonly: 'foo',
type: 'button',
// @ts-expect-error
prop: 1
};
const unionComponentProps2: ComponentProps<typeof unionComponent> = {
binding: true,
readonly2: 'foo',
href: 'https://example.com',
// @ts-expect-error
prop: 1
};
// Test that self-typed functions are correctly inferred, too (use case: language tools has its own shape for backwards compatibility)
const unionComponentProps3: ComponentProps<(a: any, b: { a: true }) => { foo: string }> = {
a: true
};
mount(unionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
readonly: 'foo',
type: 'button',
// @ts-expect-error
x: ''
}
});
mount(unionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
// @ts-expect-error wrong type
readonly: 1
}
});
mount(
unionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
mount(null as any as Component<{}>, { target: null as any });
hydrate(unionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {
binding: true,
readonly: 'foo',
type: 'button',
// @ts-expect-error
x: ''
}
});
hydrate(unionComponent, {
target: null as any as Document | Element | ShadowRoot,
// @ts-expect-error missing prop
props: {
binding: true
}
});
hydrate(
unionComponent,
// @ts-expect-error props missing
{ target: null as any }
);
// if component receives no args, props can be omitted
hydrate(null as any as Component<{}>, { target: null as any });
render(unionComponent, {
props: {
binding: true,
readonly: 'foo',
type: 'button'
}
});
// @ts-expect-error
render(unionComponent);
render(unionComponent, {
// @ts-expect-error
props: {
binding: true
}
});
render(unionComponent, {
// @ts-expect-error
props: {
binding: true,
readonly: 1
}
});
// --------------------------------------------------------------------------- *.svelte components
// import from a nonexistent file to trigger the declare module '*.svelte' in ambient.d.ts

@ -115,6 +115,10 @@ declare module 'svelte' {
*/
export type ComponentInternals = Branded<{}, 'ComponentInternals'>;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never;
/**
* Can be used to create strongly typed Svelte components.
*
@ -140,7 +144,7 @@ declare module 'svelte' {
export interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> = {},
Bindings extends keyof Props | '' = string
Bindings extends keyof UnionToIntersection<Required<Props>> | (string & {}) | '' = string
> {
/**
* @param internal An internal object used by Svelte. Do not use or modify.
@ -2177,10 +2181,10 @@ declare module 'svelte/motion' {
* const tween = Tween.of(() => number);
* </script>
* ```
*
*
*/
static of<U>(fn: () => U, options?: TweenedOptions<U> | undefined): Tween<U>;
constructor(value: T, options?: TweenedOptions<T>);
/**
* Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it.
@ -2231,7 +2235,7 @@ declare module 'svelte/reactivity' {
* ```
*/
export class SvelteDate extends Date {
constructor(...params: any[]);
#private;
}
@ -2267,12 +2271,12 @@ declare module 'svelte/reactivity' {
* {#if monkeys.has('🙊')}<p>speak no evil</p>{/if}
* ```
*
*
*
*/
export class SvelteSet<T> extends Set<T> {
constructor(value?: Iterable<T> | null | undefined);
add(value: T): this;
#private;
}
@ -2318,12 +2322,12 @@ declare module 'svelte/reactivity' {
* {/if}
* ```
*
*
*
*/
export class SvelteMap<K, V> extends Map<K, V> {
constructor(value?: Iterable<readonly [K, V]> | null | undefined);
set(key: K, value: V): this;
#private;
}
@ -2386,7 +2390,7 @@ declare module 'svelte/reactivity' {
* ```
*/
export class SvelteURLSearchParams extends URLSearchParams {
[REPLACE](params: URLSearchParams): void;
#private;
}
@ -2462,7 +2466,7 @@ declare module 'svelte/reactivity' {
*/
export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void;
class ReactiveValue<T> {
constructor(fn: () => T, onsubscribe: (update: () => void) => void);
get current(): T;
#private;
@ -2527,7 +2531,7 @@ declare module 'svelte/reactivity/window' {
get current(): number | undefined;
};
class ReactiveValue<T> {
constructor(fn: () => T, onsubscribe: (update: () => void) => void);
get current(): T;
#private;
@ -2550,7 +2554,7 @@ declare module 'svelte/server' {
? [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options?: {
props?: Omit<Props, '$$slots' | '$$events'>;
props?: NoInfer<Props>;
context?: Map<any, any>;
idPrefix?: string;
}
@ -2558,7 +2562,7 @@ declare module 'svelte/server' {
: [
component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp,
options: {
props: Omit<Props, '$$slots' | '$$events'>;
props: NoInfer<Props>;
context?: Map<any, any>;
idPrefix?: string;
}

Loading…
Cancel
Save