@_nodoc attribute for hiding symbols from the symbol graph

Sure, so let's not use block directive syntax for this purpose then?

this feels like a lot of feature redundancy, because we will now have:

  • hard attributes, like @available(*, unavailable) that lay defaults for documentation visibility

  • soft attributes, like /// documentation-visibility: public appended to the top of doccomments, that modify the original visibility

  • markdown attributes, like Biome’s @import(_:) directive, that apply even more configurations to a doccomment.

/// documentation-visibility: public 
/// @import(Baz)
/// Use ``Baz.bar()`` instead!
@available(*, unavailable, message: "use bar() instead!")
func foo()
{
}

all because we cannot do:

/// @import(Baz)
/// Use ``Baz.bar()`` instead!
#if swift(>=5.8)
@documentation(visibility: public)
#endif
@available(*, unavailable, message: "use bar() instead!")
func foo()
{
}

which would evolve into

/// @import(Baz)
/// Use ``Baz.bar()`` instead!
@documentation(visibility: public)
@available(*, unavailable, message: "use bar() instead!")
func foo()
{
}

once we get to swift 5.11 in 2023 or so.

@available does much more than documentation visibility!

And to be clear, what we are discussing is how to spell one of the features—the ultimate number of features involved doesn’t change at all whether it’s spelled one way or another.

I wouldn’t use this exact color of bikeshed—since the notation is already in a doc comment, if we stipulate that it has to be the first line it can be as terse as [hide]—but I prefer even the above vastly over:

…let alone the transitional spelling, even if that were possible today, the reason being that it places the strictly documentation-related toggles inside the documentation.

the flip side of this is that symbolgraphs do much more than gather doccomments. in fact doccomments are a pretty minor component of the symbolgraph format — among other things, handling the availability mixin (which is what we call @available from the documentation tooling side) accounts for a larger share of the tooling logic.

i’m talking more about the syntax used to pass options to these features than the features themselves. right now, we have a nice dichotomy:

  1. attributes, like @available, are for source-level constructs, and
  2. block directives, like @import, are for article-level constructs. (yes, a doccomment is an article! it is an article that is bound to its succeeding declaration by default, can be shared by multiple declarations, and could conceivably be rebound to a different symbol just like any other extension.)

@documentation(visibility: public) is useful even if no doccomment is present.

I fear we may have overcomplicated this. Back to Victoria's original post: We essentially want to productize the _-prefix rule. Note that this rule does more than just impact docs—it affects the generated interface and autocomplete.

In particular I'm concerned we will end up with too many tools and axes on which to slice the API surface of a module. Sometimes it’s appropriate to make compromises in flexibility for simplicity. We’re adding additional technical and cognitive burden when reasoning about conditionally-valid content like links, curation, and relationship-building.

Here's what I suggest:

  1. We return to Victoria's suggestion, with some bikeshedding: <access control keyword>(<visible|hidden>), e.g. public(hidden). Or alternatively as per the original proposal rename @_nodoc to @_hidden to make it clear it's not just a docs feature, it applies anywhere the symbol would normally be visible. Import access control proposes adding access control modifiers to import statements, so either proposed approach should work for exported imports.
  2. If the symbol is used in source code from within the same target or the symbol graph is built for an access control level below the level specified in the decl (e.g. internal graph for a public(hidden) symbol), we would emit the symbol into the graph and the symbol would remain available in autocomplete etc.
  3. For backwards compatibility we continue to respect the underscore-prefix rule and allow overriding with something like public(visible).
  4. Separately, I think there's value in a block directive for conditionally including symbols/articles/paragraphs in docs for particular audiences. For example, this could be used for authoring additional internal-only documentation and to David's point it'd work for any programming language. Perhaps this idea should be explored separately though.

Aside: I’m not too concerned about a documentation compiler needing to understand directives—the author of the comment is already choosing to target doc compilers with a particular feature set when they write a doc comment and we’ve recognized this as an acceptable cost of directives. It's most important that we make a best effort to design syntax that fails gracefully.

5 Likes

I feel like having the overloaded access control keywords is providing a way larger feature than we would like to introduce. Limiting our feature to impact just documentation allows us to experiment far more easily than we would if we were suddenly introducing extra syntax that affected all the places that underscored APIs are handled.

Moreover, i like the direction this thread has taken the proposal: Rather than just providing an extra hook for underscoring an API, this is a more flexible option to affect what appears in docs. If we want to generalize this later on, we can morph this into an Evolution proposal and pitch it there. For right now, i'd like to land the implementation i've been updating alongside this thread so that we can start experimenting with it.

3 Likes

I feel this is certainly true at least initially. I don’t think we need to change such a core part of the language just for documentation needs initially. However, I think using a more generic attribute name, e.g. @_visisbility(…), might be the way forward as it would give us a good starting point to see if this something we want to push through evolution and productize it as part of the core language.

I've gone ahead and merged the current version of the @_documentation(...) attribute PR. Future design enhancements can be discussed here or in a new thread. If we want to expand the feature beyond symbol graphs, we should open a full Evolution proposal for it.

For posterity, here's how it would look in action:

To change whether a symbol is shown in documentation, use the @_documentation(visibility:) attribute:

@_documentation(visibility: internal)
public class ActuallyInternalType {}

public enum MixedCase {
    case PublicOne
    case PublicTwo

    @_documentation(visibility: public)
    case _ShouldBePublic
}

The visibility: form expects an access control keyword (public, internal, private, etc). If it cannot parse one from the attribute (e.g. it was misspelled) then the compiler will emit an error.

The visibility in the attribute corresponds is checked against the "minimum access level" given to the Swift compiler when generating symbol graphs. (For example, when Xcode generates app-level documentation, it requests a minimum access level of internal so that the resulting documentation is useful for contributors to the app to see items that are available across the codebase.)

To add arbitrary metadata to the symbol graph for other tools to process, use the @_documentation(metadata:) attribute:

@_documentation(metadata: coolStuff)
public class MyCoolClass {}

@_documentation(metadata: "super-cool")
public class MySuperCoolClass{}

As the code sample shows, the given metadata can be either a plain identifier or a quoted string. The given text will be available in the symbol graph under the "metadata" property of the symbol. Swift-DocC does not (currently) use this information, but other tools could use this information for their own purposes.

4 Likes

This is awesome! Thank you for the update. The _documentation attribute is still not available in Xcode 14.1. Will it be included in the next Xcode version?

1 Like

Bumping this up. I am working on multiple SDKs with DocC and this feature would be super helpful! Kindly suggest the timeline of support on Xcode. Thanks folks!

I'm not sure the exact timing, but i think it would need to be picked up into a release/X.Y branch first (right now it's only on main). I don't think they pull stuff from main directly into an existing release branch automatically, so it would have to wait until whatever comes after 5.7 gets its release branch, whenever that happens.

Right, it’ll show up in 5.8 automatically.