You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #6970 and #6992. There's basically a variance violation in these methods, they are only correct when this is not up-casted to a base class (these methods violate LSP). There isn't really a straightforward typing rule we can use to prove this that has reasonable DX, but we can add a simple invariant to prove that there isn't a casting violation.
classExtendedTextNodeextendsTextNode{afterCloneFrom(prevNode: this): void{super.afterCloneFrom(prevNode).setNewProperty(this.getNewProperty());}}// This will throw an error about a missing `getNewProperty` method at runtime,// but passes type checks due to the up-cast to TextNodeconstextendedTextNode: TextNode=$createExtendedTextNode();extendedTextNode.afterCloneFrom($createTextNode());
We can add an invariant to check this, it still won't get caught by the type checker, but will provide for better runtime errors:
classExtendedTextNodeextendsTextNode{afterCloneFrom(prevNode: this): void{invariant(prevNodeinstanceofthis.constructor,'prevNode must be an instance of ExtendedTextNode');super.afterCloneFrom(prevNode).setNewProperty(this.getNewProperty());}}
Affected methods with this soundness violation:
afterCloneFrom
updateDOM
updateFromJSON has a similar variance violation, but it can't really be detected so simply, since the serializedNode argument is not an instance at all. We could provide a function to check the constructor type chain though.
updateFromJSON
function$checkSerializedType(node: LexicalNode,serializedNode: SerializedLexicalNode){constnodeKlass=node.constructor;constregisteredNode=$getEditor()._nodes.get(serializedNode.type);// the JSON can be from the exact type, or a more specific typeinvariant(registeredNode!==undefined&&(registeredNode.klass===nodeKlass||nodeKlass.isPrototypeOf(registeredNode.klass)),'$checkSerializedType node of type %s can not be deserialized from JSON of type %s',nodeKlass.getType(),serializedNode.type,);}
Alternative
An alternative might be to change the protocols entirely, maybe with some sort of static method called for each class when it's added to the editor and to plumb these calls through the editor in a provably type-safe way, but that's not backwards compatible. Something like this might work:
exportinterfaceUpdateDOMFunc<TextendsLexicalNode>{(node: T,prevNode: T,dom: ReturnType<T['createDOM']>,config: EditorConfig,next: ()=>boolean): boolean;}exportinterfaceUpdateFromJSONFunc<TextendsLexicalNode>{// None of this is truly type safe, these are just casts,// the correct solution is to parse(node: T,json: ReturnType<T['exportJSON']>,next: (json?: ReturnType<T['exportJSON']>)=>T): T;}exportinterfaceAfterCloneFromFunc<TextendsLexicalNode>{// No need for middleware here, we can just force all of them to be called in order(node: T,prevNode: T): void;}classNodeRegistration<TextendsLexicalNode>{// This could be used instead of static clone and static importJSONregisterCreateNode($createNode: ()=>T): void;registerAfterCloneFrom($afterCloneFrom: AfterCloneFromFunc<T>): void;registerUpdateDOM($updateDOM: UpdateDOMFunc<T>): void;registerUpdateFromJSON($updateFromJSON: UpdateFromJSONFunc<T>): void;}classTextNodeextendsLexicalNode{staticregisterNode(reg: NodeRegistration<TextNode>): void{reg.registerCreateNode($createTextNode);reg.registerUpdateDOM((node,prevNode,dom,config,next)=>{// this is the equivalent of the super call// depending on the node this might be called before, after,// or not at all based on the requirements of this subclassif(next()){returntrue;}// … some logic specific to this nodereturnfalse;});}}
I'm sure we could come up with a backwards compatible transition from one to the other, if we leave the methods somewhere on the base classes for a transitional period so that super calls work for nodes that do not support the static registration protocol.
Impact
This will improve error messages for incorrect usage of the library in places where the type checker can't help.
The text was updated successfully, but these errors were encountered:
Description
Follow-up to #6970 and #6992. There's basically a variance violation in these methods, they are only correct when
this
is not up-casted to a base class (these methods violate LSP). There isn't really a straightforward typing rule we can use to prove this that has reasonable DX, but we can add a simple invariant to prove that there isn't a casting violation.We can add an invariant to check this, it still won't get caught by the type checker, but will provide for better runtime errors:
Affected methods with this soundness violation:
updateFromJSON has a similar variance violation, but it can't really be detected so simply, since the
serializedNode
argument is not an instance at all. We could provide a function to check the constructor type chain though.Alternative
An alternative might be to change the protocols entirely, maybe with some sort of static method called for each class when it's added to the editor and to plumb these calls through the editor in a provably type-safe way, but that's not backwards compatible. Something like this might work:
I'm sure we could come up with a backwards compatible transition from one to the other, if we leave the methods somewhere on the base classes for a transitional period so that
super
calls work for nodes that do not support the static registration protocol.Impact
This will improve error messages for incorrect usage of the library in places where the type checker can't help.
The text was updated successfully, but these errors were encountered: