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] type functions that can run type code within current scope (sort of like macros) #33818

Closed
5 tasks done
trusktr opened this issue Oct 4, 2019 · 5 comments
Closed
5 tasks done
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@trusktr
Copy link
Contributor

trusktr commented Oct 4, 2019

Search Terms

Suggestion

f.e., suppose I want to augment JSX types.

Examples

We can write

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-component': React.DetailedHTMLProps<MyComponentAttributes, MyComponent>
    }
  }
}

but imagine if we could write:

AddJSXType<'my-component', MyComponentAttributes, MyComponent>

which would be a "type function" call that is essentially like a macro.

The implementation of AddJSXType could be something like

typefunc AddJSXType<N, A, C> =>
  declare global {
    namespace JSX {
      interface IntrinsicElements {
        [k: N]: React.DetailedHTMLProps<A, C>
      }
    }
  }

This would prevent every Custom Element component from having to copy/paste all that boilerplate.

Is there currently another way?

Use Cases

opportunity to make some repetitive boilerplate-heavy things simpler/terser.

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@trusktr trusktr changed the title type functions that can augment externals types [suggestion] type functions that can run type code within current scope (sort of like macros) Oct 15, 2019
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds and removed Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Oct 17, 2019
@RyanCavanaugh
Copy link
Member

This would require a ground-up rearchitecting of how binding and name resolution works, along with at least one additional full tree walk of every source file; I can't possibly imagine we would undertake that kind of effort unless this sort of thing was vastly more common and difficult to do.

@trusktr
Copy link
Contributor Author

trusktr commented Nov 8, 2019

Ah, it may have been neat if it weren't so complicated.

At the moment the boilerplate for each of my custom elements looks like this:

import * as React from 'react'
import {Component} from './Component'

interface TimelineAttributes extends React.HTMLAttributes<Timeline> {
  time?: number
}

class Timeline extends Component<TimelineAttributes>(HTMLElement) {
  // ... skipped for brevity ...
}

customElements.define('time-line', Timeline)

declare global {
  interface HTMLElementTagNameMap {
    'time-line': Timeline
  }
}

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'time-line': React.DetailedHTMLProps<TimelineAttributes, Timeline>
    }
  }
}

I wonder if there is a better way.

Suppose the previous example can be simplified to

import * as React from 'react'
import {Component} from './Component'

interface TimelineAttributes extends React.HTMLAttributes<Timeline> {
  time?: number
}

class Timeline extends Component<TimelineAttributes>(HTMLElement) {
  // ... skipped for brevity ...
}

customElements.define('time-line', Timeline)
defineIntrinsicElement<'time-line', TimelineAttributes, Timeline>

where defineIntrinsicElement is a "type function" whose implementation is omitted.

Or maybe there's some other possibility than "type function". The main idea is to have some mechanism to make it possible to define types in bulk. The above idea is sort of like a "macro" but fully baked into the type system, intellisense, etc.

@trusktr
Copy link
Contributor Author

trusktr commented Feb 1, 2021

The preprocessor idea in #4691 would help, but it adds much too many possibilities.

What if TS allowed a limited type of preprocessor, call "type function" for example, so that something like

typefunction defineIntrinsicElement<A, B, C> {
  declare global {
    namespace JSX {
      interface IntrinsicElements {
        ${A}: React.DetailedHTMLProps<${B}, ${C}>
      }
    }
  }
}

##defineIntrinsicElement<'my-component', MyComponentAttributes, MyComponent>

would be the same as having written

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-component': React.DetailedHTMLProps<MyComponentAttributes, MyComponent>
    }
  }
}

It would have limited functionality, so people won't be inventing new language within TypeScript. For example, a requirement could be that "interpolations" would only be allowed in certain places so that arbitrary names maps to places where arbitrary names would be allowed to appear.

@typescript-bot
Copy link
Collaborator

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

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 21, 2023
@trusktr
Copy link
Contributor Author

trusktr commented Oct 6, 2024

Should this issue,

be marked as Too Complex also?

I think a better name for this feature could be "type-only macros". The other macro feature requests, for example these,

are too complex indeed, because this wanted to have unlimited macros even for outputting JavaScript code, associating types and methods via new decorator-like macros, etc, etc.

But the idea in this thread is limited to types only.

Another syntax idea is:

type macro defineElement<E, C, A> => {
  declare module 'solid-js' {
    namespace JSX {
      interface IntrinsicElements {
        ${E}: Pick<${C}, ${A}>
      }
    }
  }

  declare global {
    interface HTMLElementTagNameMap {
      ${E}: ${C}
    }
  }
}

It can only be declared in any location where a type can be declared, and it can only be expanded in locations where a type can be declared. The generic-like arguments ("type expression arguments"? I'm not sure the terminology) coud follow some rules like being limited to certain exporession types or locations, not arbitrary syntax.

defineElement<"my-element", MyElementAttributes, MyElement>

Additionally there'd be a rule where by it can be merged with a function, and can serve to combine a JavaScript function call with a type definition. The function call has to happen in a location where a statement could be placed, and would not be allowed in any expression-only location.

type macro defineElement<E, C, A> =>
  // ... same as before ...

function defineElement<E, C, A = 'foo' | 'bar'>(name: E, Class: C) {
  customElements.define(name, Class)
}

// Use it:
defineElement('my-element', MyElement)

The call to defineElement('my-element', MyElement) will expand as if the following was written at the call site:

 // The call still needs to happen, for plain JavaScript
defineElement('my-element', MyElement)

// But it would be as if this were written sibling to the function call:
declare module 'solid-js' {
  namespace JSX {
    interface IntrinsicElements {
      'my-element': Pick<MyElement, 'foo' | 'bar'>
    }
  }
}
declare global {
  interface HTMLElementTagNameMap {
    'my-element': MyElement
  }
}

Then we could write 100 elements without repeating all that boilerplate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests

3 participants