Skip to content

Conversation

@hukkin
Copy link
Contributor

@hukkin hukkin commented Feb 26, 2021

This PR makes SyntaxTreeNode.children and SyntaxTreeNode.parent read-only properties, as it seems that's by far the least complicated way of making type annotations work expectedly when extending the class. Those properties shouldn't be rewritten anyways so I guess this is not a bad thing
Edit: This PR simply makes SyntaxTreeNode.children and SyntaxTreeNode.parent @propertys so that they can be type annotated in a way where the getter return type TypeVar is automatically bound to type of self.

@codecov
Copy link

codecov bot commented Feb 26, 2021

Codecov Report

Merging #131 (d96f9b7) into master (0cd5dc3) will decrease coverage by 0.07%.
The diff coverage is 90.90%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #131      +/-   ##
==========================================
- Coverage   96.51%   96.43%   -0.08%     
==========================================
  Files          72       72              
  Lines        3183     3202      +19     
==========================================
+ Hits         3072     3088      +16     
- Misses        111      114       +3     
Flag Coverage Δ
pytests 96.43% <90.90%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
markdown_it/tree.py 94.11% <90.90%> (-1.25%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0cd5dc3...d96f9b7. Read the comment docs.

@hukkin hukkin changed the title DRAFT: Attempt to make SyntaxTreeNode type annotations work when subclassing IMPROVE: Make SyntaxTreeNode type annotations work when subclassing Feb 26, 2021
@hukkin hukkin changed the title IMPROVE: Make SyntaxTreeNode type annotations work when subclassing 👌 IMPROVE: Make SyntaxTreeNode type annotations work when subclassing Feb 26, 2021
@hukkin hukkin marked this pull request as ready for review February 26, 2021 03:37
@hukkin
Copy link
Contributor Author

hukkin commented Feb 26, 2021

@chrisjsewell would be great if we could sort this out before the next release. Making the two attributes read-only is a breaking change, but if this is dealt with before the next release, then it will never be release breaking change.

There's two commits in this PR. The first commit is an alternative self-contained working solution, and doesn't require making the attributes read-only. It however makes the class a Generic that you'd always have to subclass like

class MyTree(SyntaxTreeNodeBase["MyTree"]):
    pass

or alternatively always annotate or instantiate like

tree = MyTree["MyTree"]()

At least I couldn't find any way to to make the type checker automatically bind the TypeVar to the subclass type so that this wouldn't be needed.

Soo, that's very awkward so I think I much prefer the state after the second commit.

EDIT: this comment is outdated

@hukkin
Copy link
Contributor Author

hukkin commented Feb 26, 2021

If you're wondering why this has to be so complicated in the first place, haha, it's because many interfaces (method input or outputs, public class attrs) in this class reference the type of self, and that type changes when subclassing. This isn't too common so that's why type annotations are not always so hard.

closing: Token


_T = TypeVar("_T", bound="SyntaxTreeNode")
Copy link
Member

Choose a reason for hiding this comment

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

why _T? it would be better from a readability perspective to use something like NodeType

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like its a convention to name TypeVars as T (or if private, _T). I think this is because its short, and because the same TypeVar could be reused in various contexts having a different meaning in each. Here setting the bound sort of limits any reasonable reusability though.

That being said, I don't oppose renaming at all if you think thats better 😄

Copy link
Member

Choose a reason for hiding this comment

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

yes I think so thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to _NodeType

# Empty list unless a non-empty container, or unnested token that has
# children (i.e. inline or img)
self.children: List["SyntaxTreeNode"] = []
self.children = []
Copy link
Member

Choose a reason for hiding this comment

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

should this not be self._children?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There really is no difference, but I think using the public API is less brittle in case someone ever changes the @property setter

Copy link
Member

@chrisjsewell chrisjsewell Mar 4, 2021

Choose a reason for hiding this comment

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

I think all internal variables should be set in __init__. TBH I'm surprised flake8 does not fail this, pylint definitely does

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The internal vars are set in init, but via the @Property, flake8 understands this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reluctantly made this change 😄


# Root node does not have self.parent
self.parent: Optional["SyntaxTreeNode"] = None
self.parent = None
Copy link
Member

Choose a reason for hiding this comment

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

should this not be self._parent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same as self._children 😄


@classmethod
def from_tokens(cls, tokens: Sequence[Token]) -> "SyntaxTreeNode":
def from_tokens(cls: Type[_T], tokens: Sequence[Token]) -> _T:
Copy link
Member

Choose a reason for hiding this comment

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

do cls and self really need to be annotated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, the type checker binds a meaning to _T based on context. Here it gets the context from cls (it knows its type) so it deduces that the return type is the same as that.

E.g. if you call node = MyTreeNode.from_tokens(), the type checker then knows that node is MyTreeNode and if the type is something different (another subclass) the type checker binds the TypeVar to that.

Copy link
Member

Choose a reason for hiding this comment

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

ok cheers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To clarify, if you dont annnotate cls, the type checker doesn't have any context useful to binding the TypeVar so the return type will be Any.

@hukkin
Copy link
Contributor Author

hukkin commented Mar 4, 2021

@chrisjsewell I added the necessary @overloads to type annotate __getitem__ properly in c0b698a

@chrisjsewell chrisjsewell merged commit de09ccf into executablebooks:master Mar 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants