GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [ID-Prep] PR4 - Preserve types nodes from type assertions in declaration emit by dragomirtitian · Pull Request #57589 · microsoft/TypeScript

Conversation

@dragomirtitian

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) commented Mar 1, 2024

Copy link
Copy Markdown
Contributor

Fixes #57587

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dragomirtitian
Added tests for using type from assertion in declarations.
297ba1b
@typescript-bot TypeScript Bot (typescript-bot) added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Mar 1, 2024
dragomirtitian
Titian Cernicova-Dragomir (dragomirtitian) commented Mar 1, 2024
View reviewed changes
Comment thread tests/cases/compiler/hugeDeclarationOutputGetsTruncatedWithError.ts
type manyprops = `${props}${props}`;

export const c = null as any as {[K in manyprops]: {[K2 in manyprops]: `${K}.${K2}`}}; No newline at end of file
export const c = () => null as any as {[K in manyprops]: {[K2 in manyprops]: `${K}.${K2}`}}; No newline at end of file

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) Mar 1, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to preserve the original intent of the test (test what happens when huge type nodes need to be printed).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dragomirtitian
Allow extraction of type from type assertions for declaration emit
9399b32
@dragomirtitian
Titian Cernicova-Dragomir (dragomirtitian) force-pushed the ts-verbatim-declrations-from-assertions branch from f996402 to 9399b32 Compare March 1, 2024 11:45
@RyanCavanaugh
Ryan Cavanaugh (RyanCavanaugh) requested a review from Ron Buckton (rbuckton) March 1, 2024 17:44
@typescript-bot TypeScript Bot (typescript-bot) added For Backlog Bug PRs that fix a backlog bug and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Mar 1, 2024
jakebailey
Jake Bailey (jakebailey) reviewed Mar 1, 2024
View reviewed changes
Comment thread tests/baselines/reference/accessorDeclarationEmitVisibilityErrors.errors.txt Resolved

Uh oh!

There was an error while loading. Please reload this page.

jakebailey
Jake Bailey (jakebailey) reviewed Mar 1, 2024
View reviewed changes
Comment thread src/compiler/transformers/declarations.ts
const resolver = context.getEmitResolver();
const options = context.getCompilerOptions();
const { noResolve, stripInternal } = options;
const strictNullChecks = getStrictOptionValue(options, "strictNullChecks");

@jakebailey Jake Bailey (jakebailey) Mar 1, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels weird to have this detail in the transform; right now the only options that are used are ones explicitly related to declaration emit (noResolve/stripInternal).

My impression is that isolatedDeclarations is not assumed to know the compiler options; is the case that we can treat strictNullChecks as true for emit with the assumption that undefined/null are just going to be ignored by non-strict users?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) Mar 1, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bit of strictNullChecks that needs to be taken into account. This is used here in the context of inferring optional property declaration types. If strictNullChecks is on we need to add undefined to the type otherwise we don't. Without taking this into account any local inference optional declarations will not be possible:

class X { 
	// x?: 1 | 2 | undefined; with !strictNullChecks
	// x?: 1 | 2; // with !strictNullChecks
	x? = 1 as 1 | 2 
}

Playground Link

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@jakebailey Jake Bailey (jakebailey) Mar 1, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure but if a user doesn't have strictNullChecks enabled, aren't the two equivalent? Since the way that strictNullChecks=false works is to effectively treat null/undefined as though they don't exist.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) Mar 1, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are usually, but if you take the declaration from a project with !strictNullChecks and use them in a project exactOptionalPropertyTypes then the missing | undefined will make a difference.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) Mar 1, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, it might actually be an improvement. Because a !strictNullChecks project should be fine with a undefined.

I can change this to always add | undefined but the second problem is that it will make emit different between the case when we take the type from the checker (which does not add undefined) and when we try to get the type from the assertion. The solution would be to add the |undefined always form the checker as well.

Maybe this should be a different PR though? One that always adds |undefined to optional properties even if !strictNullChecks

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@jakebailey Jake Bailey (jakebailey) Mar 1, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I see what you mean, in that this PR is only trying to touch assertions and therefore making this code unconditional would not change any other declarations?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) Mar 1, 2024

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Even it's final form, !isolatedDeclarations some types will still come from the checker, I don't think adding undefined just sometimes would be a good thing.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dragomirtitian
Accessor declared type was not visited
4f63418
@dragomirtitian
Titian Cernicova-Dragomir (dragomirtitian) force-pushed the ts-verbatim-declrations-from-assertions branch from c47b97d to 4f63418 Compare March 1, 2024 19:10
@dragomirtitian
Merge remote-tracking branch 'remotes/origin/main' into ts-verbatim-d…
c84eeb0
…eclrations-from-assertions
rbuckton
Ron Buckton (rbuckton) suggested changes Mar 4, 2024
View reviewed changes
Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread src/compiler/transformers/declarations.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread tests/cases/compiler/verbatim-declarations-assertions.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

@dragomirtitian
Incorporated feedback
3833bf5
rbuckton
Ron Buckton (rbuckton) reviewed Mar 5, 2024
View reviewed changes
Comment thread tests/baselines/reference/duplicatePropertiesInTypeAssertions01.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

rbuckton
Ron Buckton (rbuckton) reviewed Mar 5, 2024
View reviewed changes
Comment thread tests/baselines/reference/duplicatePropertiesInTypeAssertions02.js Resolved

Uh oh!

There was an error while loading. Please reload this page.

@dragomirtitian
Infer type from assertion for export assignment.
5506934
@dragomirtitian
Titian Cernicova-Dragomir (dragomirtitian) force-pushed the ts-verbatim-declrations-from-assertions branch from 4ffc67a to 5506934 Compare March 5, 2024 17:55
@dragomirtitian
Added test for JS assertions (not affected)
83c3715
rbuckton
Ron Buckton (rbuckton) approved these changes Mar 6, 2024
View reviewed changes
jakebailey
Jake Bailey (jakebailey) previously approved these changes Mar 6, 2024
View reviewed changes

@jakebailey Jake Bailey (jakebailey) left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't feel too great about declaration emit directly knowing about strictNullChecks as I'm not sure how we are expecting third party emitters to also know this, but, I guess it's fine.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@rbuckton

Ron Buckton (rbuckton) commented Mar 6, 2024

Copy link
Copy Markdown
Contributor

I still don't feel too great about declaration emit directly knowing about strictNullChecks as I'm not sure how we are expecting third party emitters to also know this, but, I guess it's fine.

That's a good point. Aside from strictOptionalProperties, what would be the harm in adding | undefined regardless of strictNullChecks? In strictNullChecks: false we would ignore the undefined anyways.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jakebailey

Jake Bailey (jakebailey) commented Mar 6, 2024

Copy link
Copy Markdown
Member

I think the reason I think it's "fine" is that dependence is already present today, just that it's hidden by calls to the checker. So as a whole, this PR is just an emit improvement and not actually regressing in that way. Third party tools are already able to resolve tsconigs, I just think it's frustrating that declaration emit is sensitive to it at all (but I'm also in the camp of wanting to ditch strictNullChecks=false altogether!)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dragomirtitian

Titian Cernicova-Dragomir (dragomirtitian) commented Mar 6, 2024

Copy link
Copy Markdown
Contributor Author

I still don't feel too great about declaration emit directly knowing about strictNullChecks as I'm not sure how we are expecting third party emitters to also know this, but, I guess it's fine.

That's a good point. Aside from strictOptionalProperties, what would be the harm in adding | undefined regardless of strictNullChecks? In strictNullChecks: false we would ignore the undefined anyways.

My biggest issue is that it will be added sometimes. When the type is extracted in declarations, we will add undefined but when it comes from the type checker it will not be added:

type P = number | undefined
declare function getP(): P
class X {
    f1? = getP() as P; // P | undefined  with this PR
    f2? = getP(); // P
}

This seems like a strange difference that will make people open issues.

I think our options are:

  1. Always add | undefined to optional fields, even if the type is inferred - A big emit change.
  2. Only add it for type that come from the declaration emit - An inconsistent emit change.
  3. We can just let go of this and not extract types for optional fields in declaration emit (and make it an error in ID) - Worse DX in ID
  4. Allow the declaration emit to know about strictNullChecks.

I am not 100% we can keep strictNullChecks completely out of declaration emit in future PRs. When we add inference for objects, something like let o = { n: null, u: undefined } is typed as { n: any; u: any; } without strictNullChecks and { n: null, u: undefined } with it so. So the declaration emit will need to know what type to use there (unless we ban null and undefined there too)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@rbuckton

Ron Buckton (rbuckton) commented Mar 6, 2024

Copy link
Copy Markdown
Contributor

We'll discuss this PR in the design meeting on Friday and assuming there are no other concerns around strictNullChecks, should be able to merge then.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) changed the title Preserve types nodes from type assertions in declaration emit [ID-Prep] PR4 - Preserve types nodes from type assertions in declaration emit Mar 7, 2024
@rbuckton

Ron Buckton (rbuckton) commented Mar 8, 2024

Copy link
Copy Markdown
Contributor

TypeScript Bot (@typescript-bot) pack this

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@typescript-bot

TypeScript Bot (typescript-bot) commented Mar 8, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@typescript-bot

TypeScript Bot (typescript-bot) commented Mar 8, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Hey Ron Buckton (@rbuckton), I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/160236/artifacts?artifactName=tgz&fileId=5EB08BDD23C04BCCF2F81BCC237A591E7B83FA0F429777473E0AAE6247CC3D5002&fileName=/typescript-5.5.0-insiders.20240308.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

weswigham
Wesley Wigham (weswigham) requested changes Mar 8, 2024
View reviewed changes

@weswigham Wesley Wigham (weswigham) left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, this is something we could/should have done even without isolated declarations, and we've done things like this in the past. We already have machinery for cloning/reusing input type nodes in new locations inside the emit resolver (usually for signatures copied from one location to another by structural inlining) - this logic should be moved into it, too, so all the guardrails around node copying are in one place (and there are a fair few). Over in createTypeOfDeclaration and createReturnTypeOfSignatureDeclaration they both defer to nodeBuilder.typeToTypeNode - within the node builder, we have a serializeExistingTypeNode (and parent function serializeTypeForDeclaration used extensively by the JS declaration emitter) function that adds a lot of guard rails onto this node copying process, because, well, there's a lot that needs to be checked for that currently isn't in this PR.

IMO, this logic should be exposed on the nodeBuilder and then used by the EmitResolver's createTypeOfDeclaration and createReturnTypeOfSignatureDeclaration instead of typeToTypeNode directly (rather than built directly into the declarations transformer). That way it's easy to leverage the existing caching and safe copying mechanisms we have, and to reuse the logic in the JS declaration emitter (which, I dunno if you wanna consider testing that out of scope or not).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@jakebailey
Jake Bailey (jakebailey) dismissed their stale review March 8, 2024 19:30

Outdated; see Wesley's comment

@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) mentioned this pull request Mar 12, 2024
Design Meeting Notes, 3/12/2024 #57756
Closed
@robpalme Rob Palmer (robpalme) mentioned this pull request Mar 13, 2024
--isolatedDeclarations for standalone DTS emit #47947
Closed
5 tasks done
@dragomirtitian

Titian Cernicova-Dragomir (dragomirtitian) commented Mar 13, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Wesley Wigham (@weswigham)

The goal of the PRs to align emit is to extract more information directly from the initialisers of declarations that have them. To this end we will extract extract types from:

  1. Assertions (current PR)
  2. Function expressions / Arrow functions
  3. Array literals in const assertions. (This includes any primitive literals used within)
  4. Object literals - in const and mutable contexts. (This includes any primitive literals used within)

The general idea is that the types in these cases are trivial enough that by looking at the initialization expressions it is possible to derive the type node, without having to go through the type checker. This approach may yield a more readable type in some cases but not necessarily. The end goal is to allow external tools to perform the same transformations, and to be able to guarantee that these transformations are indeed just syntactical ones that don’t use any type information that is not local to the initialiser.

Option 1: Transform initialisers to type nodes in the type checker (node builder)

This approach will have all the logic for extracting the types in the same place as type nodes are synthesized from type checker types. Copying of existing type nodes already happens in serializeExistingTypeNode.

Advantages

  1. The serializeExistingTypeNode already does copying of nodes and tracking their usage. So this new functionality can reuse the same logic
  2. Can be used in JS declaration emit as well.
  3. Benefits from caching ? (Not really sure about this one)

Disadvantages

The declaration transform will not be runnable without the type checker. This creates several problems:

  1. It becomes harder for other implementers to reason about what is the scope and functionality of types from initialisers.
  2. Colocation of type from initializer extraction with the type checker makes it easy for maintainers down the line to reach for type information that is not guaranteed to be syntactically derivable just the initialiser.
  3. It becomes impossible to prove that TypeScript is actually living up to the guarantee of generating these types without needing the type information. So "bugs" may arise where we find a third-party emitter cannot reasonably replicate TypeScript's emit.
  4. Any transpileDeclaration that is added (now or later) will have to instantiate the type checker (transpileModule already does this and is 3-6 times slower than just running the JS transforms in isolation). To be explicit: if we do not choose Option 1, we could easily introduce and expose a fast transpileDeclaration() in the initial release. The only reason it is not in scope today is due to the guidance we have received. We would love to ship this API if it were permitted/appreciated.
  5. Any future implementation of transpileDeclaration() that does not rely on the type checker will need to duplicate the logic to extract type from the initialiser .

Option 2: Transform initialisers in the declaration emit

This is the approach in the current PR. This approach will have all the logic for extracting types from initialisers in the declaration transform. The declaration transform already does other purely syntactic transformation of original source to ensure the correct types in the declaration (ex: constructor class fields are transformed to parameters and fields, expressions in heritage clauses are moved to variables that are independently typed, default exports are moved to variables)

Advantages

  1. Reuses machinery to track the type use that already is in the declaration transform. This already existed as it was necessary when copying existing type node from source (they do not go through serializeExistingTypeNode).
  2. All the code that required for emit in isolated declaration mode is in the transform, so it’s easier for other implementer to understand.
  3. Since any communication with the type checker needs to happen though the emit resolver it’s harder for someone to accidentally use forbidden type knowledge in isolated declaration mode
  4. Allows us to easily compare output of typescript with and without the type checker, ensuring that the declarations are equivalent (or if they are not we can document why the difference occurs). This has been invaluable in identifying cases where typescript does more work than is obvious at first glance (such as triple slash references)
  5. transpileDeclaration() can be fast as it does not need the type checker.
  6. A transpileDeclaration() implementation will require reimplementing a much smaller subset of what the emit resolver does. We already have a working version of this that is the backbone for our testing

Disadvantages

  1. JS declaration emit will not benefit from these changes
  2. ?

Final thoughts

While for the current PR the extraction of the type happens from an assertion, for the other kinds of type from initializer transformations, there is no actual type node to extract from. But rather we go through the literal and create a new appropriate type if possible (reusing as much of the original AST). Putting this second way of getting the type in the typechecker does not really seem like a win. While in some cases the resulting types will be more faithful to the original source, the extra type information can also result in better types from the type checker.

Just having the errors without actually extracting the type is also an option, but that makes the validation of external emitters much harder, as TypeScript may emit different declaration files (even if they are mostly semantically equivalent).

Extraction from the initializer is needed for isolated declarations, but if it is always better for types to come from this source is not necessarily clear, and it’s a question that is out of scope for the current effort.

Option 3: Factor out the shared logic

If we still want to reuse some of this logic in the type checker, maybe a better approach would be to factor out the type node from initializer logic into a separate component that can be reused in both serializeExistingTypeNode and in the declaration emit. This would allow the declaration transform to still be invoked without a type checker, preserving the advantages of the second approach.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@weswigham Wesley Wigham (weswigham) mentioned this pull request Mar 14, 2024
Look for usable type nodes in associated expressions for declaration emit #57772
Merged
@dragomirtitian

Titian Cernicova-Dragomir (dragomirtitian) commented Mar 18, 2024

Copy link
Copy Markdown
Contributor Author

Closed in favor of #57772

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) closed this Mar 18, 2024
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 16, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@weswigham Wesley Wigham (weswigham) weswigham requested changes
@jakebailey Jake Bailey (jakebailey) jakebailey left review comments
+1 more reviewer
@rbuckton Ron Buckton (rbuckton) rbuckton approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

[ID-Prep] Preserve type nodes from type assertions in declarations

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@dragomirtitian @rbuckton @jakebailey @typescript-bot @weswigham
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.