Skip to content

Feature Request: Macros #4892

Closed as not planned
Closed as not planned

Description

@Gaelan

This one might be waaaaaay out of scope, but I think it is worth proposing. The idea here is to add support for macros, functions that run at compile time, taking one or more AST nodes and returning an AST node or array of AST nodes. Examples/use cases: (Syntax off the top of my head, open to better ideas)

Interface-Based Validation

// validation.macros.ts
function interfaceToValidatorCreator(interface: ts.InterfaceDeclaration): ts.Statement {
    // Would return something like "const [interface.name]Validator = new Validator({...});",
    // using types from the interface
}
macro CreateValidator(interface: ts.InterfaceDeclaration) {
    return [interface, interfaceToValidator(interface)];
}
// mainFile.ts
/// <macros path='./valididation.macros.ts' />
@#CreateValidator // Syntax 1: Decorator-Style
interface Person {
    name: string;
    age: number;
}

PersonValidator.validate(foo)

Type Providers

// swagger.macros.ts
macro GetSwaggerClient(url: ts.StringLiteral): AssertionExpression {
    // return something like "new SwaggerClient([url]) as SwaggerClientBase & {...}" where
    // ... is an object creating the methods generated from the URL.
}
// mainFile.ts
/// <macros path='./swagger.macros.ts' />
var fooClient = #GetSwaggerClient("http://foo.com/swagger.json"); // Syntax 2: Function call syntax
fooClient.getPeople((people) => {
    people.every((person) => console.log(person.firstName + "," + person.lastName);
});

Conditional Compilation

// conditional-compilation.macros.ts
macro IfFlagSet(flagName: ts.StringLiteral, code: ts.Statement[]): ts.Statement[] {
    return process.env[flagName.string] ? code : []
}
// mainFile.ts
/// <macros path='./compilation.macros.ts' />
#IfFlagSet("DEVELOPMENT") { // Syntax 3: Language Construct-Like (multiple arguments can be passed in parentheses)
    expensiveUnnecessarySanityCheck()
}

Notes

  • Macros would run right after parsing. Not sure how we would deal with macros that need type information.
  • This would make running tsc on unknown code as dangerous as running unknown code. It might be good to require a --unsafeAllowMacros argument, not settable from a tsconfig.json.
  • It might be worth nothing in the docs that the AST format may change at any time, or something along those likes
  • The macro keyword would probably compile to a function, followed by a ts.registerMacro(function, argumentTypes, returnType call.
  • Macros must be typed as returning a AST interface. This means that functions creating ASTs will probably need to have an explicit return type (or a calling function could have an explicit return type.
    • Alternatively, we could consider giving the kind property special treatment in macros.ts files.
  • Just because a proposed syntax looks like a normal typescript construct doesn't mean it behaves like one. #Foo(interface Bar{}) is valid syntax, as long as there is a macro named Foo that takes an interface.
    • Exception: The Decorator syntax might need to be a bit more choosy (no decorating 1 + 1, but decorating Interfaces, interface items, functions, etc. should be fine.
  • This issue is likely to be updated quite a bit. For a log of changes, see the gist

Activity

  1. mhegazy commented on Sep 21, 2015

    @mhegazy
    Contributor

    duplicate of #3136?

  2. Gaelan commented on Sep 21, 2015

    @Gaelan
    Author

    Mohamed Hegazy (@mhegazy) I don't think so. Support for macros as AST->AST functions would let us do anything you could do with a type provider (just return an AssertionExpression, see the second example in the issue text), but also conditional compilation (return the passed code if the condition is true, otherwise do nothing), as well as general boilerplate reduction.

  3. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Sep 21, 2015
  4. arkdemon commented on Nov 28, 2015

    @arkdemon

    +1

  5. fgalassi commented on Dec 14, 2015

    @fgalassi

    +1

  6. elbarzil commented on Dec 23, 2015

    @elbarzil

    +1 (I almost expected it to work with sweet.js.)

  7. ulfryk commented on Dec 29, 2015

    @ulfryk

    +1

  8. nisimjoseph commented on Jan 9, 2016

    @nisimjoseph

    +1

  9. joergwasmeier commented on Jan 10, 2016

    @joergwasmeier

    You can also look at the haxe macro system how they implemented it. http://haxe.org/manual/macro.html

  10. soycabanillas commented on Jan 14, 2016

    @soycabanillas

    +1

  11. Zorgatone commented on Jan 22, 2016

    @Zorgatone

    +1 👍

  12. carywreams commented on Feb 10, 2016

    @carywreams

    Question
    Could this be provided by a pre-compile hook; between slurping the .ts file and depositing the .js file?

    Possible Benefits

    • Allows for competing macro processors, eventually yielding a best-in-class ? or 500 of them...
    • Natural requirement is production of valid TypeScript; but that's now on the user
    • Allowed to evolve separately and distinctly along a rigorous path for macro definitions and implementations (ala regular expressions)

    Of course, I could be missing some fundamental concept of macros and compilers, rather than just attempting to break up the traditional view.

  13. lazdmx commented on Feb 25, 2016

    @lazdmx

    +1

  14. gvkhna commented on Apr 12, 2016

    @gvkhna

    +1

  15. 80 remaining items

  16. hazae41 commented on Oct 20, 2023

    @hazae41

    Bun "macros" are not functions that can deeply modify the code but just regular JS functions evaluated at compile-time (they must output JS values)

    What most people need is a function that can be called anywhere, and that will be output some arbitrary code (classes, functions, React components, code blocks)

    For example, instead of creating a high-order-class, a macro would generate all the classes you need

  17. hazae41 commented on Oct 23, 2023

    @hazae41

    I have built something that can solve many use cases, without using AST

    https://github.com/hazae41/saumon

    Macros are like normal functions, but the preprocessor will replace their call by the string value they return

    function $log$(x: string) {
      return `console.log("${x}")`
    }
    
    $log$("hello world")
    • Get a random value at compile-time
    function $random$(): number {
      return `${Math.random()}` as any
    }
    
    const x = $random$() * 100
    • Fetch some data at compile-time
    function $fetch$<T>(url: string): T {
      return (async () => {
        const response = await fetch(url)
        const object = await response.json()
    
        return JSON.stringify(object)
      })() as any
    }
    
    export const data = $fetch$<{ id: number }>("https://dummyjson.com/products/1")
    • Inject something in a class
    function $log$(x: string) {
      return `log() {
        console.log("${x}")
      }`
    }
    
    class Something {
    
      /**
       * @macro 
       * $log$("hello world")
       */
    
    }
  18. added
    Out of ScopeThis idea sits outside of the TypeScript language design constraints
    and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Oct 23, 2023
  19. RyanCavanaugh commented on Oct 23, 2023

    @RyanCavanaugh
    Member

    This comment is correct; macros are definitely not in scope with our current definition of what we would/would not do in TypeScript.

  20. alshdavid commented on Nov 1, 2023

    @alshdavid

    Is this something that could be implemented by the community as an extension to tsc and the TypeScript language server?

    More generally, does the TypeScript compiler and TypeScript language server have the ability to be "exteneded" to include custom syntax - like the inclusion of compile time macros?

  21. RyanCavanaugh commented on Nov 2, 2023

    @RyanCavanaugh
    Member

    No

  22. owl-from-hogvarts commented on Nov 3, 2023

    @owl-from-hogvarts

    Why no? What about typescript tranformers? ttypescript as example

  23. nonara commented on Nov 3, 2023

    @nonara

    owl from hogvarts (@owl-from-hogvarts)

    An extension like what's described here would require extending the compiler codebase itself. ttypescript and ts-patch allow you to add transformers to the compilation process, but they don't allow you to transform the compiler — not yet, anyway.

  24. RyanCavanaugh commented on Nov 3, 2023

    @RyanCavanaugh
    Member

    Why no?

    I was asked a factual question and answered it 🤷‍♂️

  25. owl-from-hogvarts commented on Nov 3, 2023

    @owl-from-hogvarts

    So then sorry. It seems I have misunderstood the question)

    Anyway, my though is that macroses potentially can be implemented via transformers api 🤔

  26. alshdavid commented on Sep 13, 2024

    @alshdavid

    One thought I had was to implement Rust style macros as an swc plugin expressed as magic comments. Consider the following examples:

    Macro Unpacks to
    const value = react!(<div>Hi</div>)
    const value = createElement('div', {}, ['Hi'])

    Another case is proc macros (not a real case but as an example)

    Proc Macro Unpacks to
    @component!({
      template: <div>
        <div ng-for={const item of items}>{item}</div>
      </div>
    })
    class MyComponent {
      items = [1,2,3,4,5]
    }
    class Foo {
      items = [1,2,3,4]
    
       constructor() {
         this[TemplateSmb] = createElement('div', {}, [/** ... */])
       }
    }

    Both can be described with magic-comment style notation however the content within the macro function parameters would be considered normal JavaScript and custom syntax (like jsx) would break type checking and the procedural macro would not have types

    const value = react/**!*/(<div>Hi</div>)
    /** #[derive(Default)] */
    type Foo = {
      bar: string
    }

    Without type checking, the feature is DOA. This is sad because it's a very valuable and flexible way to expand the language without putting the burden on the TypeScript team to maintain

    e.g. React JSX should be maintained by the React team, styled components could be a macro, Angular decorators could probably be a custom procedural macro and frameworks like Svelte and Vue that use custom file extensions would have a better alternative for expressing their templating syntax

    After working in Rust for the last 2 years, I am craving macros in TypeScript. We already have a pseudo macro - jsx. So close yet so far haha

  27. alshdavid commented on Dec 1, 2024

    @alshdavid

    I'm sorry for reopening this but I really feel it would make a big difference to how TypeScript is used

    #60645

  28. ariccio commented on Dec 1, 2024

    @ariccio

    I mean, let's be real, as you mentioned above, if even rust has macros it's goofy that typescript doesn't. I might expect it to be a reasonable fight-on-this-hill for a high end very high level proprietary functional language like the Wolfram/mathematica language, but typescript isn't at that extreme level of first class metaprogramming just yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions