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

Proposal: Comptime. Optimization hints for Preprocessors #49052

Closed
5 tasks done
lucsoft opened this issue May 10, 2022 · 7 comments
Closed
5 tasks done

Proposal: Comptime. Optimization hints for Preprocessors #49052

lucsoft opened this issue May 10, 2022 · 7 comments
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@lucsoft
Copy link

lucsoft commented May 10, 2022

Suggestion

πŸ” Search Terms

compiler preprocessor inline comptime

βœ… Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Add syntax hints that preprocessor can use.
Typescript should add this so developers would have a common interface to tell there toolchains (in this context: typescript aware preprocessors) which functions or expression can be evaluate at compile time.
Preprocessor could then evaluate the runtime code and make it run at compiletime and serve its response static.

This Suggestion is not the first idea proposed here, but it tries to fix the problem the other ones had.

🚫 Non Goals

Typescript should not evaluate the code itself. (Which #26534 suffered from)
Typescript should not throw any comptime errors

πŸ“ƒ Motivating Example

This Idea is nothing new. Zig is one of the biggest languages which have implemented this feature.

And comptime is a great feature for the web, as code size matters (like for loading speeds). Yet we want readable code, which often suffers from more code. more readable code.

https://kristoff.it/blog/what-is-zig-comptime/
https://ziglang.org/documentation/master/#comptime

πŸ’» Use Cases

1. comptime for setting a variable

New Valid Syntax:

const compiledAt = comptime new Date().getTime()
// or
const compiledAt = new Date().getTime() as comptime

Compiles directly to:

const compiledAt = new Date().getTime()

A Typescript aware preprocessor would turn this into:

const compiledAt = 1652206435966
2. comptime getting used in a function

New Valid Syntax:

comptime async function readConfigFile() {
      const data = await Deno.readTextFile("config.json");
      return JSON.parse(data);
}

console.log((await readConfigFile()).version)

Compiles directly to:

async function readConfigFile() {
      const data = await Deno.readTextFile("config.json");
      return JSON.parse(data);
}

console.log((await readConfigFile()).version)

A Typescript aware preprocessor would turn this into:

console.log("1.0.0-beta.1");
3. comptime validation depending on function paramets

New Valid Syntax:

// "comptime id" declares that this parameter needs to be known at compile time (aka preprocessor time)
function registerView(comptime id: string) {
      comptime: {
             if (!id.includes("-"))
                    throw new Error("Invalid ID");
             if (id.toUpperCase() != id)
                    throw new Error("Invalid ID");
      }
      // Normal code goes here [ ... ]
}

registerView("Hey-100") // Valid for Typescript
registerView("HEY-100") // Valid for Typescript

Compiles directly to:

function registerView(id) {
      comptime: {  // A "Label" is a valid javascript syntax
             if (!id.includes("-"))
                    throw new Error("Invalid ID");
             if (id.toUpperCase() != id)
                    throw new Error("Invalid ID");
      }
      // Normal code goes here [ ... ]
}

registerView("Hey-100") // Valid for Javascript, Throws a runtime error (which could lead to a unknown bug)
registerView("HEY-100") // Valid for Javascript

A Typescript aware preprocessor would turn this into:

function registerView(id: string) {
      // Normal code goes here [ ... ]
}

registerView("Hey-100") // Invalid for the Preprocessor, Throws a compiletime error (which would identify the bug directly)
registerView("HEY-100") // Valid for the Preprocessor
@lucsoft lucsoft changed the title Proposal: Comptime. Optimization hints for Preprocessor. Proposal: Comptime. Optimization hints for Preprocessors May 10, 2022
@MartinJohns
Copy link
Contributor

You should take a critical look at the viability checklist, because this is definitely not in line with TypeScripts goals.

I also fail to see why you would need a new keyword for this, when a comment would suffice. The "TypeScript aware preprocessor" could just check for the presence of the comment.

@lucsoft
Copy link
Author

lucsoft commented May 11, 2022

Hey Martin!

Typescript does this so often. specifically like adding import type just so preprocessor can better understand what the data flow is.
A comment could have done the trick. Yet still it wasn't done.

Also:

  • A comment also introduces inconsistency.
  • Just look at linters there is eslint-disable-next-line prettier-ignore tslint:disable-next-line and so much more, just for doing the same concept.
  • They introduce so much noise in an readable code.
  • A comment should be a comment not a "preprocessor command"

@fatcerberus
Copy link

I’m really confused. You say:

Evaluate runtime code and make it run at compiletime and serve its response static.

But then later you go on to say this:

Typescript should not evaluate the code itself. (Which #26534 suffered from)

Which one is it? Because I can’t tell what the difference is between this and #26534.

@lucsoft
Copy link
Author

lucsoft commented May 11, 2022

Which one is it? Because I can’t tell what the difference is between this and #26534.

I updated the description. πŸ˜‰
It was an explainer of how the preprocessor could work

@MartinJohns
Copy link
Contributor

Typescript does this so often. specifically like adding import type just so preprocessor can better understand what the data flow is.

But import type is not an expression-level syntax. Goal 8 states avoiding to add new expression-level syntax, so checking that point in the viability checklist is wrong.

Just look at linters there is eslint-disable-next-line prettier-ignore tslint:disable-next-line and so much more, just for doing the same concept.

Shows that the concept works just fine. :-)

IMO adding a new keyword to the TypeScript language that third-party tools may use is the wrong approach and will just lead to confusion when users expect it to do something.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds labels May 11, 2022
@RyanCavanaugh
Copy link
Member

This doesn't really fit into our design parameters. There are a huge number of questions that would need to be answered here to make this work in the context of a type system, like what happens when you indirect one of these expressions, what the lexical environment is when these functions are evaluated, and so on.

If I had a program that really needed this, I'd write a pass-through precompile<T>(x: T): T function, then whatever build tool that's evaluating these could look for calls to that function and do "the right thing", whatever that is. You could even make functions that could only be precompiled by writing something like

// Write once
type Precompiled<T> = { value_do_not_access: T };
function precompile<T>(value: Precompiled<T>): T { return value.value_do_not_access; };
function makePrecompiled<T>(value: T): Precompiled<T> {
  return { value_do_not_access: value };
}

// Use elsewhere
function getVersion(): Precompiled<string> {
  return makePrecompiled("3.1.4.1");
}

const s: string = precompile(getVersion());

This makes the semantics of this extremely clear, which I would think is very important to avoid surprises since writing JS that looks like JS but does something non-JSy is bad.

@DanielRosenwasser DanielRosenwasser added Out of Scope This idea sits outside of the TypeScript language design constraints and removed Too Complex An issue which adding support for may be too complex for the value it adds labels May 13, 2022
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Out of Scope" 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants