Skip to content

Support class Foo < Data.define(:a, :b) as a documented superclass - #1705

Open
svyatov wants to merge 1 commit into
lsegal:mainfrom
svyatov:feat/data-define-class-superclass
Open

Support class Foo < Data.define(:a, :b) as a documented superclass#1705
svyatov wants to merge 1 commit into
lsegal:mainfrom
svyatov:feat/data-define-class-superclass

Conversation

@svyatov

@svyatov svyatov commented Aug 14, 2026

Copy link
Copy Markdown

Description

class Foo < Data.define(:a, :b) currently registers with no superclass. The parser logs [warn]: in YARD::Handlers::Ruby::ClassHandler: Undocumentable superclass (class was added without superclass), and the rendered docs claim the class inherits Object. #1533 reports this exact case. #1600 added the constant form Foo = Data.define(:a, :b) and invited this change:

Note: the support of the class Foo < Data.define(:a, :b) form was not added by intention because I don't want to encourage this style of programming and it does not appear in the Data documentation. If one has a strong opinion that this style must be supported, they're free to do it in a separate PR.

Here is that separate PR. The strong opinion: current tooling forces the inheritance form on some projects, whatever style one prefers.

  • ruby-lsp reads .rb and never sig/, so hover docs on members need a documented reader per member: def a = super. That reader works only in the inheritance form. In the block form, Data.define(:a) do def a = super end raises NoMethodError: super: no superclass method 'a' at runtime, because the block runs on the same class that defines the reader.
  • Steep type-checks only the inheritance form. In the block form it attributes the methods to ::Object (do block inside class with in Data.define not correctly identified soutaro/steep#963), and the rbs guide docs/data_and_struct.md records the same preference for inheriting.
  • The Data documentation not showing the form also holds for class Foo < Struct.new(...), which YARD has long supported.

Changes:

  • ClassHandler#parse_superclass resolves Data.define(...) to Data, next to the existing Struct.new branch. The warning is gone and the ancestry renders correctly.
  • Members become read-only attributes through the path the constant form already uses. The loop from ConstantHandler#process_dataclass moved to StructHandlerMethods#create_readers, and both handlers call it.
  • The legacy parser resolves the superclass through one added regex alternative. Member specs are gated to the Ripper parser, as Support A = Data.define(:a, :b, :c) #1600's specs are.

Verified against a real tree: a gem that generates 361 such classes from the Telegram Bot API spec. Before: 361 warnings. After: zero warnings, every class renders Data ancestry with read-only member attributes, and explicitly documented readers in class bodies keep their own docstrings.

Completed Tasks

  • I have read the Contributing Guide.
  • The pull request is complete (implemented / written).
  • Git commits have been cleaned up (squash WIP / revert commits).
  • I wrote tests and ran bundle exec rake locally (if code is attached to PR).

ClassHandler has no branch for Data.define, so the class registers with
no superclass, logs an "Undocumentable superclass" warning, and renders
as inheriting Object. The constant form gained support in lsegal#1600, which
left the class form to a separate PR.

The superclass now resolves to Data. Members become read-only attributes
through the reader-only path the constant form uses, lifted into
StructHandlerMethods#create_readers. The legacy parser gains superclass
resolution only; member specs are gated to the Ripper parser, as
lsegal#1600's are.
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.

1 participant