Reject abstract members in non-abstract classes#1647
Conversation
A non-abstract class or module previously accepted `abstract` property and method declarations. Because such an enclosing type is instantiable, the abstract member can never be guaranteed an implementation, so the contradiction surfaced only as a runtime error when the member was accessed, if at all. Validate this while building the AST: declaring an `abstract` member is now an error unless its enclosing class or module is also `abstract`, with the message pointing at the `abstract` keyword. This matches the existing behavior for abstract classes and mirrors how Java and Kotlin treat abstract members. The `moduleMethodModifiers` pkl-doc test fixture relied on the old behavior; make it an `abstract module` and regenerate its expected documentation output. Fixes apple#1614
bioball
left a comment
There was a problem hiding this comment.
Thanks! I think this solution makes sense. Another approach here could have been to defer the abstract check when the member is assigned to or accessed, but this feels more principled and catches obvious bugs earlier.
Note that this doesn't fully address #1614; this still works:
abstract class Foo {
abstract bar: Int
}
class Qux extends Foo {}
qux: Qux = new {
bar = 5
}Waiting for eyes from @HT154 and @stackoverflow before merging this.
Co-authored-by: Daniel Chao <daniel.h.chao@gmail.com>
|
@vinayakj592 Class |
@bioball I see what you're saying, I will cover that while working on #1262 , I think this can be merged irrespective of that. |
|
Yes, agree! |
|
By the way, what does the typical review and merge cycle look like here? How long does it usually take for a PR to get reviewed and merged? I’m planning to start contributing more actively, so I was also wondering if there are any discussion channels or community spaces apart from GitHub Discussions where contributors collaborate and ask questions. |
|
Feel free to join the Pkl community discord (see https://pkl.community/); us maintainers also hang out on there! This PR is blocked on a review from @stackoverflow. |
|
Thanks @bioball |
stackoverflow
left a comment
There was a problem hiding this comment.
Look good. Thanks for the contribution.
|
@vinayakj592 FYI: I explored a solution for #1262 in #1690. I think there's a couple interesting open questions to cover, though (see that PR's description) |
Fixes #1614.
Context
A non-abstract
class(ormodule) was allowed to declareabstractproperties and methods.Because such an enclosing type is instantiable, an
abstractmember there can never be guaranteedan implementation — so the contradiction surfaced only as a runtime error when the member was
accessed (
Cannot invoke abstract method), or not at all.This makes it a compile-time error to declare an
abstractmember unless its enclosing class ormodule is also
abstract. This is consistent with how Pkl already rejects instantiating an abstractclass, and mirrors how Java and Kotlin treat abstract members.
Before
After
Implementation
AstBuildernow validates, while building the AST, that a non-abstract class/module declares noabstractmembers. The check runs in bothvisitClassandvisitModule, and the error points atthe
abstractkeyword.abstractMemberInNonAbstractClasserror message.Scope: classes and modules
The issue describes classes; I applied the same rule to modules as well, since a module is a class
in Pkl and a non-abstract module is likewise directly evaluatable. Happy to narrow this to classes
only if you'd prefer — it's a one-line change either way.
The
moduleMethodModifierspkl-doc test fixture declared an abstract method at non-abstract modulelevel (relying on the old behavior); it's updated to an
abstract module, and its expecteddocumentation output is regenerated.
Tests
LanguageSnippetTestserror cases: abstract property in a class, abstract method in a class,and abstract member in a module.
./gradlew buildpasses (pkl-coreandpkl-docincluded).