Skip to content

Inherit doc comments from default implementations of inherited protocols #73066

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

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Apr 16, 2024

For example when we have

protocol BaseProtocol {}

extension BaseProtocol {
  /// Say hello
  func hello() { }
}
struct MyStruct: BaseProtocol {
  func hello() {}
}

Then MyStruct.hello should inherit the doc comment from the implementation of BaseProtocol.hello. Currently no doc comment is associated with MyStruct.hello.

rdar://126240546


The changes in offsets in the test cases is because init(rawValue:) now inherits the doc comment of RawRepresentable, which shifts the lines in generated interfaces around.

@@ -426,24 +426,24 @@ class CommentProviderFinder final {
return std::nullopt;
}

/// Check if there is an inherited protocol that has a default implementaiton
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Check if there is an inherited protocol that has a default implementaiton
/// Check if there is an inherited protocol that has a default implementation

@@ -0,0 +1,34 @@
protocol BaseProtocol {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could add a test for the case where there is both a requirement and a default

@ahoppen ahoppen force-pushed the inherit-doc-comment-from-protocol-default-impl branch from 619a27a to bdfe29f Compare April 17, 2024 20:59
@ahoppen
Copy link
Member Author

ahoppen commented Apr 17, 2024

@swift-ci Please smoke test

Comment on lines +432 to +434
NominalTypeDecl *nominalType =
dyn_cast_or_null<NominalTypeDecl>(VD->getDeclContext()->getAsDecl());
Copy link
Contributor

Choose a reason for hiding this comment

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

What if VD is defined in an extension? Do we want to support that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

That only handles protocol extensions, right? I was thinking of e.g a struct extension

@ahoppen ahoppen force-pushed the inherit-doc-comment-from-protocol-default-impl branch from bdfe29f to a174859 Compare April 18, 2024 03:40
Comment on lines -56 to -58
// `RemoteP.P` also has an extension with a default implementation for `extraFunc` that does have
// docs, but overriding it here should prevent those from appearing

Copy link
Member Author

@ahoppen ahoppen Apr 18, 2024

Choose a reason for hiding this comment

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

@QuietMisdreavus This comments seems to imply the exact opposite of what I want to achieve in this PR. Is there any reason why we didn’t want to inherit documentation from extensions?

For context, the concrete example I want to fix with this PR is that this implementation of _ArrayProtocol.filter https://github.com/apple/swift/blob/c531f2914fda0b84d8af091a0081799101f76d9b/stdlib/public/core/ArrayType.swift#L73-L78 should inherit the documentation from the documentation of Sequence.filter, which is implemented in an extension https://github.com/apple/swift/blob/5df0f053d95c1143ac34ac88faafdfa3902a3224/stdlib/public/core/Sequence.swift#L717-L739

Copy link
Contributor

Choose a reason for hiding this comment

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

cc @franklinsch (and possibly also @bitjammer for the underlying discussion of inheriting docs in the first place)

I think it should respect the presence of the -skip-inherited-docs flag, but otherwise i don't remember off the top of my head.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we already respect the -skip-inherited-docs flag because we’re not calling into getDocCommentProvidingDecl if it is set and this PR only modifies getDocCommentProvidingDecl

https://github.com/apple/swift/blob/9fdb0e79e2dab72fdc01f8fa50d9cc249a48a5a0/lib/SymbolGraphGen/Symbol.cpp#L359-L365

Comment on lines 464 to 468
if (!memberComparisonTy->matches(vdComparisonTy, TypeMatchFlags::AllowOverride)) {
continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be the other way around? e.g allowing:

protocol P {}
extension P {
  /// hello
  func foo() -> Int? { 0 }
}
struct S: P {
  func foo() -> Int { 0 }
}

But not:

protocol P {}
extension P {
  /// hello
  func foo() -> Int { 0 }
}
struct S: P {
  func foo() -> Int? { 0 }
}

Not that these are really overrides anyway, but if we want similar type-matching rules, then I think it makes sense to flip the ordering.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, yes. I thought that I had this case covered with the throws case but apparently throws works either way 🤷🏽

Comment on lines +441 to +444
nominalType->lookupQualified(nominalType, DeclNameRef(VD->getName()),
VD->getLoc(), NLOptions::NL_ProtocolMembers,
members);
Copy link
Contributor

Choose a reason for hiding this comment

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

Thought of a couple more edge cases :)

  1. static mismatch
protocol P {}
extension P {
  /// hello
  static func foo() {}
}
struct S: P {
  func foo() {}
}
func bar(_ x: S) {
  x.foo()
}
  1. enum case matching a function (if we want this to work it would need to be a static function)
protocol P {}
extension P {
  /// hello
  func foo(_ x: Int) -> E { .foo(0) }
}

enum E: P {
  case foo(Int)
}

func bar(_ x: E) {
  E.foo(0)
}
  1. this is quite silly, but technically this would match:
class C {
  /// hello
  struct foo {}
}
class D: C {
  var foo: C.foo.Type { C.foo.self }
}

func bar(_ x: D) {
  let y = x.foo
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Good cases, thanks. Case 3 does inherit but I decided to not care and not add a test case for it. If it broke, I don’t think we would care.

Comment on lines +450 to +454
if (auto fnTy = vdComparisonTy->getAs<AnyFunctionType>()) {
// Strip off the 'Self' parameter.
vdComparisonTy = fnTy->getResult();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@ahoppen ahoppen force-pushed the inherit-doc-comment-from-protocol-default-impl branch from a174859 to 0795102 Compare April 18, 2024 22:45
@ahoppen
Copy link
Member Author

ahoppen commented Apr 18, 2024

@swift-ci Please smoke test

@ahoppen ahoppen force-pushed the inherit-doc-comment-from-protocol-default-impl branch from 0795102 to d2b3942 Compare April 19, 2024 05:19
@ahoppen
Copy link
Member Author

ahoppen commented Apr 19, 2024

@swift-ci Please smoke test

@@ -426,24 +426,49 @@ class CommentProviderFinder final {
return std::nullopt;
}

/// Check if there is an inherited protocol that has a default implementation
/// of `VD` with a doc comment.
std::optional<ResultWithDecl> findDefaultProvidedDecl(const ValueDecl *VD) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just duplicating logic in the witness checker. Default witnesses are recorded in the ProtocolDecl.

See TypeChecker::inferDefaultWitnesses() and ProtocolDecl::getDefaultWitness().

Copy link
Member Author

Choose a reason for hiding this comment

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

That’s not sufficient though. We aren’t dealing with protocol requirements here, just default methods on a protocol.

For example in the following test, InheritedProtocol.hello() needs to inherit the documentation from BaseProtocol.hello but there is no protocol requirement that hello() can be a witness for. Any suggestion of how to implement that without my implementation?

protocol BaseProtocol {}
extension BaseProtocol {
  /// Say hello
  func hello() { }
}

protocol InheritedProtocol: BaseProtocol {}
extension InheritedProtocol {
  func hello() { }
}

For example when we have

```swift
protocol BaseProtocol {}

extension BaseProtocol {
  /// Say hello
  func hello() { }
}
struct MyStruct: BaseProtocol {
  func hello() {}
}
```

Then `MyStruct.hello` should inherit the doc comment from the implementation of `BaseProtocol.hello`. Currently no doc comment is associated with `MyStruct.hello`.

rdar://126240546
@ahoppen
Copy link
Member Author

ahoppen commented Aug 3, 2024

@swift-ci Please smoke test Linux

@ahoppen ahoppen merged commit 45e9534 into swiftlang:main Aug 7, 2024
3 checks passed
@ahoppen ahoppen deleted the inherit-doc-comment-from-protocol-default-impl branch August 7, 2024 17:01
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.

5 participants