-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
cloneNode should return sub type, not Node #283
cloneNode should return sub type, not Node #283
Comments
I assume that you have something like the following code var d: Document;
var x = d.cloneNode(); // 'x' is of type 'Node' but you wanted the resulting type of
One thing we could do is make elem.cloneNode<Document>(); and have that return a Something that allows you to get exactly what you want would be to define your own helper function so that you generally won't have to supply the generic parameter since it can be inferred. function cloneNode<T extends Node>(node: T) {
return <T>node.cloneNode();
}
var d: Document;
var x = cloneNode(d); // 'x' is of type 'Document' |
@DanielRosenwasser, thus can be nicely solved with generic "this"argument, as in #285. |
My comment was misleading. I meant to say that there is no way to automatically indicate to the compiler that a method should return the statically known subtype of a type. In fact, we could include a signature on all subtypes of |
Agree that this is basically the same issue as #285. |
@DanielRosenwasser @RyanCavanaugh Now that TS has interface Node {
cloneNode(deep?: boolean): this;
} It'd technically be a lie though, since it returns a new object, not itself. Not sure if that matters. Otherwise would it be okay to add an overload of |
I think |
Wouldn't |
The thing is interface Cloneable {
clone(): this;
}
class Foo implements Cloneable {
clone(): this {
// Error. Can work around by asserting to `any`,
// but still semantically wrong if not overridden by every derived class.
return new Foo();
}
} It doesn't matter as much for |
I had a similar issue with. As I understand it, the issue here is that you cannot return anything except
The problem with this is that then you have this problem:
However, I have a case where I had a fixed set of classes where I needed to do this clone operation. If you are willing to be disciplined and ensure that each subclass implements
As I said, I think this is safe (seemed to work for me). There are semantic issues with "assignability", I didn't really dig into this to see if there are corner cases where this would break anything. But as far as I can tell, as long as you follow this discipline (always provide a Comments? |
Because
I don't think it would be appropriate to rely on a hack like this in the standard libraries? Looks like what we need is something like
Or with the
This ought to be safe, even if another class inherits that method:
In other words, Would that work?? |
(JSDoc) Workaround / for future reference: use manual cast // cloneNode produces Node, @see https://github.com/Microsoft/TypeScript/issues/283
var a = document.cloneNode(true);
a.location; // Property 'location' does not exist on type 'Node'.
// workaround:
var b = /** @type {Document} */ (document.cloneNode(true));
b.location; // OK Learned from #25028. |
5 years old :) Is this ever going to be fixed? |
Do I understand correctly that solution which involve cloneNode<T extends Node = Node>(this: T, deep?: boolean): T; won't be considered and expected fix is to add specific And those who create custom element will have to define their own types for this method? |
Is it not possible to extract the class type from interface Node {
cloneNode(deep?: boolean): new Constructor<this>; // Not real
} |
@fregante see microsoft/TypeScript-DOM-lib-generator#302 (comment) . It is working solution (see comment above for sorter version), but "That PR would make compile time and memory usage worse for everybody, so we can't take it." (even if they don't need PR that adds overrides with concrete types is in place and waiting for review. |
Right, that's what I was looking to solve.
Oh, I see that now. Easy solution but does it work correctly on custom elements? |
No, custom elements declaration would have return type of element that they extend (which will be close enough for some cases) but need own override to have exact type :( I'm not sure that it is possible to do so without making |
As in @myfonj example, the trick is to wrap expression in brackets /** @type {HTMLTableRowElement} */
const $templateRow = document.createElement('tr')
/** @type {HTMLTableRowElement} */
const $rowClone1 = $templateRow.cloneNode(true)
// Error: `Type 'Node' is missing the following properties from type 'HTMLTableRowElement': ...`
/** @type {HTMLTableRowElement} */
const $rowClone2 = ($templateRow.cloneNode(true))
// No errors |
Unfortunately, we had to revert microsoft/TypeScript-DOM-lib-generator#811 because it makes type parameters that extend HTMLElement invariant where they were previously covariant. See microsoft/TypeScript-DOM-lib-generator#842
|
This works: interface Node extends EventTarget {
cloneNode(deep?: boolean): this;
} Not sure if The only problem I see is |
@clshortfuse |
There's also There's not much information about the performance drain, so I can't speak to if |
This continues to be a problem. Could someone reassess fixing this? If there are still problems with more complete solutions, please add a specific version of cloneNode for Element and HTMLElement. If cloneNode returned Element from an Element (even if no changes for more specific types), it would still fix a bunch of code and be a huge usability improvement. |
When I do
someElement.cloneNode()
, I want the returned element to be the same type assomeElement
, but right now it's returning typeNode
.The text was updated successfully, but these errors were encountered: