Skip to content
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

Make TEnumBlock behave as a scope #354

Merged
merged 2 commits into from
Aug 22, 2024
Merged

Make TEnumBlock behave as a scope #354

merged 2 commits into from
Aug 22, 2024

Conversation

Morriar
Copy link
Collaborator

@Morriar Morriar commented Aug 6, 2024

To represent T::Enum subclasses like this one:

class MyEnum < T::Enum
  enums do
    # Some comments
    A = new
    B = new
  end
end

We currently use a special node called TEnumBlock that only accepts an array of names representing the constants like A, B. On top of being unnecessarily restrictive and complex, it doesn't allow storing the comments attached to each enum constant.

In reality, enums do .. end is nothing more than a block that can accept constants but also other types of nodes. This PR changes the superclass of TEnumBlock to be a Tree that can contain any kind of nodes including Const to represent the A, B etc. This means we can now attach comments to the constants and parse the TEnum properly.

This is a breaking change as we change the API to build TEnum which may break some clients.

@Morriar Morriar added bugfix Fix a bug breaking-change Change breaking retro-compatibility labels Aug 6, 2024
@Morriar Morriar self-assigned this Aug 6, 2024
@Morriar Morriar requested a review from a team as a code owner August 6, 2024 20:59
@Morriar Morriar requested review from amomchilov and egiurleo August 6, 2024 20:59
@Morriar Morriar force-pushed the at-fix-parse-tenum-consts branch from f0a9004 to 2bb2d07 Compare August 7, 2024 15:52
Copy link
Member

@paracycle paracycle left a comment

Choose a reason for hiding this comment

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

The changes look fine to me, but I can't understand how one is supposed to extract all enum members from the resulting TEnumBlock object? Do you need to find all the constants defined in that scope with the value new?

Comment on lines 386 to 390
scope = TEnumBlock.new(loc: node_loc(node), comments: node_comments(node))
current_scope << scope
@scopes_stack << scope
visit(node.block)
@scopes_stack.pop
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we be checking that the enums call has actually been supplied a block and no arguments?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call, I added code to handle malformed enums calls as arbitrary sends. I also added a few tests around this case.

@Morriar
Copy link
Collaborator Author

Morriar commented Aug 7, 2024

The changes look fine to me, but I can't understand how one is supposed to extract all enum members from the resulting TEnumBlock object? Do you need to find all the constants defined in that scope with the value new?

Good point, I added a TEnum#members method to make gathering the enum constants super easily:

enum = TEnum.new("Enum") do |enum|
  enum << TEnumBlock.new do |block|
    block << Const.new("A", "new")
    block << Const.new("B", "new")
  end
end

assert_equal(["A", "B"], enum.members)

@Morriar Morriar requested a review from paracycle August 7, 2024 16:35
Comment on lines +393 to +397
current_scope << Send.new(
message,
parse_send_args(node.arguments),
loc: node_loc(node),
comments: node_comments(node),
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to make sure I'm understanding -- if arguments are passed in, we treat enums as a regular method call? (Which makes sense to me)

Copy link
Member

Choose a reason for hiding this comment

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

Yes:

class Foo < T::Enum
  enums("A", "B", "C")
end

should just be a call to the enums class method on Foo (whatever that may be).

lib/rbi/model.rb Outdated
# Find the names defined by the enum.
sig { returns(T::Array[String]) }
def members
nodes.grep(Const).select { |const| const.value == "new" }.map(&:name)
Copy link
Member

Choose a reason for hiding this comment

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

What about cases where we might have:

class Foo < T::Enum
  enums do
    A = new("A")
  end
end

Is this not a concern for RBI files, I wonder?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, it should find these ones too.

I updated the implementation and test to accept any constant definition that matches /^new(\(.*\))?$/

@Morriar Morriar force-pushed the at-fix-parse-tenum-consts branch from e71066d to 1682623 Compare August 12, 2024 16:46
lib/rbi/model.rb Outdated
# Find the names defined by the enum.
sig { returns(T::Array[String]) }
def members
nodes.grep(Const).select { |const| const.value =~ /^new(\(.*\))?$/ }.map(&:name)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry if this is pedantic, but I think we need to consider cases without parens as well:

A = new "String"

Breaking change on the TEnumBlock constructor signature.

Signed-off-by: Alexandre Terrasa <alexandre.terrasa@shopify.com>
Signed-off-by: Alexandre Terrasa <alexandre.terrasa@shopify.com>
@Morriar Morriar force-pushed the at-fix-parse-tenum-consts branch from 1682623 to fd81dbf Compare August 21, 2024 19:17
@Morriar
Copy link
Collaborator Author

Morriar commented Aug 21, 2024

Following our conversation earlier I removed the members helper as we don't have a clean way to ensure the RBI::Const is actually a valid T::Enum member.

Let's revisit this once we need it 👍

@Morriar Morriar merged commit 6555ec5 into main Aug 22, 2024
8 checks passed
@Morriar Morriar deleted the at-fix-parse-tenum-consts branch August 22, 2024 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Change breaking retro-compatibility bugfix Fix a bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants