cloneNode Return Type Must Be Active Class, Not Base Class (Node)#1723
cloneNode Return Type Must Be Active Class, Not Base Class (Node)#1723andria-dev wants to merge 1 commit intomicrosoft:mainfrom
Conversation
The `cloneNode` return type is incorrect. The type before this change indicated that `cloneNode` returned a `Node`, which then broke any class that extended `Node`. In reality, `cloneNode` will return an instance of whatever the current class type is, a.k.a. `this` (`DocumentFragment#cloneNode` returns a `DocumentFragment`, not a `Node`).
|
Thanks for the PR! This section of the codebase is owned by @saschanaz - if they write a comment saying "LGTM" then it will be merged. |
This comment has been minimized.
This comment has been minimized.
|
Have you read and fixed the issues from this item? |
|
@HolgerJeromin, I will test that later today. But that issue should be fixed as we aren't manually overriding each subclass'es |
|
Manual overridding was a workaround to reduce performance problem: #220 (comment) |
|
Ah, my mistake, @saschanaz. I missed that. If there's no possible workaround that is both performant and covariant, what is the project's focus, performance or accuracy? Making the change to |
|
You can run |
|
@saschanaz I took 50 samples before and after the change and averaged each sample set. Results
So it looks like an average total increase of 4 milliseconds on my system. MethodologyThe results were aggregated with some PowerShell. I ran the following script under the main branch, and then I changed Write-Output "Files,Lines,Identifiers,Symbols,Types,Instantiations,Memory used,I/O read,I/O write,Parse time,Bind time,Check time,Emit time,Total time" | Out-File -FilePath ..\before.csv; 1..50 | ForEach-Object { (npx tsc --diagnostics --lib es5 baselines/dom.generated.d.ts | ForEach-Object { ($_ -Split ':')[1].Trim() -ireplace '(s|k)' }) -Split "`n" -Join "," | Out-File -Append -FilePath ..\before.csv } |
|
@HolgerJeromin I did a little reproduction, and it looks like // Same type parameter as Tablesorter.
interface A<T = HTMLElement> {
x?: T;
}
type An<T = HTMLElement> = A<T>;
type ANode = A<Node>;
type AnElement = An<Element>;
type AnHTMLElement = An;
type AnHTMLAnchorElement = An<HTMLAnchorElement>;
let node: ANode = {};
let element: AnElement = {};
let htmlElement: AnHTMLElement = {};
let htmlAnchorElement: AnHTMLAnchorElement = {};
// All of the return types are correct!
let clonedNode: Node = node.x?.cloneNode()!;
let clonedElement: Element = element.x?.cloneNode()!;
let clonedHtmlElement: HTMLElement = htmlElement.x?.cloneNode()!;
let clonedHtmlAnchorElement: HTMLAnchorElement = htmlAnchorElement.x?.cloneNode()!;
htmlElement = htmlAnchorElement; // Covariance works! 👍🏻
htmlElement = element; // Contravariance doesn't work (as expected). |
|
@saschanaz Just wanted to follow up with a ping. This was my conclusion:
A 4 millisecond difference seems much more acceptable than the previous 700 millisecond difference. If you have any other concerns please let me know. |
|
I'm very happy to see work done on this and since it looks like perf impact is minimal, hope this will get merged soon. This would be a huge improvement for front end devs using typescript. |

The
cloneNodereturn type is incorrect as per #283, #302, microsoft/TypeScript#17818 and #1578. The type before this PR indicated thatcloneNodereturned aNode, which then broke any class that extendedNode. In reality,cloneNodewill return an instance of whatever the current class type is, a.k.a.this(DocumentFragment#cloneNodereturns aDocumentFragment, not aNode).This PR solves this issue by changing the return type of
Node#cloneNodetothis.