Documenting source language specific parameters and return values

Hello,

The upcoming 6.0 release of Swift-DocC has a handful of enhancements for documenting API with representations in multiple source languages. In this post I'd like to talk about one such enhancement: validation and processing of documentation for parameters and return values. This is currently opt-in, controlled by a --enable-experimental-parameters-and-returns-validation flag.

There are two parts to this enhance that each solve a different problem:

  • Different language representations of an API can have different parameters and return values.
  • Sometimes when we update an API we forget to document new parameters or return values.

Different parameters in different language representations

One primary example of API with different parameters and return values is throwing functions in Swift that correspond to Objective-C methods with error parameters.

For example: @objc func doSomething(with someValue: Int) throws with one parameter and no return values corresponds to -(BOOL)doSomethingWith:(NSInteger)someValue error:(NSError **)error with two parameters and one return value.

It's common to document parameters and return values alongside the API declaration which means that depending on what source language the API is defined in, the other language representation may have too many or too few parameters documented.

By default, DocC will display all documented parameters and return values for all language representations of that API. However, when DocC is passed the --enable-experimental-parameters-and-returns-validation flag it will check the documented parameters and return values against each language representation's signature (information emitted by the compilers into their respective symbol graph file). If a parameter or return value doesn't exist in one language representation then it won't be displayed in that language's version of the documentation page.

For example, this Objective-C method will display both its parameters and the return value on the Objective-C version of its documentation page but only the first parameter on the Swift version of its documentation page

/// A method that does something.
/// - Parameters:
///   - someValue: A description of this number.
///   - error: On input, a pointer to an error object. If an error occurs, this pointer is set to an object containing information about the error.
/// - Returns: `YES` if doing something was successful, or `NO` if an error occurred.
- (BOOL)doSomethingWith:(NSInteger)someValue
                  error:(NSError **)error;

This works well for API defined in Objective-C—where there are "extra" parameters or return values—but it doesn't help with the missing parameter or return value documentation if the API is defined in Swift.

To address this—specifically for throwing Swift API that correspond to an Objective-C method with an error parameter—DocC will add a generic description for the error parameter and extend the return value documentation to describe the Objective-C specific behavior. For example, consider this method in Swift that corresponds to the Objective-C method above:

/// A method that does something.
/// - Parameter someValue: A description of this number.
@objc func doSomething(with someValue: Int) throws { }

Since the Objective-C API has an extra "error" parameter and a BOOL return value, DocC will display an extra parameter and return value description on the Objective-C version of this page.

error
On input, a pointer to an error object. If an error occurs, this pointer is set to an actual error object containing the error information.

and a synthesized return value documentation with the generic description:

Return Value
YES if successful, or NO if an error occurred.

If the Swift API has a non-optional return value that corresponds to an optional return value in Objective-C, for example:

/// A method that returns something.
/// - Returns: Description of the return value.
@objc func returnSomething() throws -> String { "" }

then DocC will add a new paragraph to the authored return value documentation to describe that the return value may be nil if an error occurs.

If an error occurs, this method returns nil and assigns an appropriate error object to the error parameter.

Both these behaviors are controlled by the --enable-experimental-parameters-and-returns-validation flag.

Documenting new parameters when API is updated.

By default, DocC lets you document any parameter or return value for any symbol, in any order, potentially more than once. However, when DocC is passed the --enable-experimental-parameters-and-returns-validation flag it will check the documented parameters and return values against each language representation's signature.

Note:
If none of the symbol's parameters have documentation DocC will not warn about adding parameter documentation. However, if some of the parameters have documentation, DocC will warn about documenting the other parameters as well. We believe that this strikes a good balance in highlighting parameters that the developer intended to document without overwhelming the project with warnings.

API without function signature data

Both these behaviors rely on the compiler emitted "function signatures" for each symbol. If a symbol doesn't have a function signature—either because the symbol graph is from a tool that doesn't emit function signatures or because the specific symbol isn't a function-like symbol kind—DocC will behave as before and use the authored parameter and return value documentation without any processing or validation. This allows symbols in custom symbol graphs to have parameters and return value documentation.

One open question is what the right behavior is when a symbol has signature information in one language representation but not in another. For example, this C function is refined as a property in Swift, which isn't function-like and doesn't have a "function signature".

/// Returns whether a circle has zero radius.
/// - Parameter circle: The circle to examine.
/// - Returns: `YES` if the specified circle is empty; otherwise, `NO`.
BOOL TLACircleIsEmpty(TLACircle circle) NS_SWIFT_NAME(getter:Circle.isEmpty(self:));

Hiding the parameter and return value documentation for the Swift property (which is correct) would have the tradeoff that other tools that create symbol graph files need to emit function signatures going forward if the API they represent is also available in other languages.

How to try it

  • Use a toolchain from either the main or release/6.0 branches, created after February 14th. You can download such a toolchain from the downloads page.
  • Pass the --enable-experimental-parameters-and-returns-validation flag to DocC, either by adding it to the end of your swift package generate-documentation call or by adding it to the Other DocC Flags build setting (OTHER_DOCC_FLAGS) in your Xcode project.

If you have symbols with different parameters in different language representations, you should now see only the applicable parameters and return values documented in each language's version of that symbol's documentation page.

If you have symbols with extra parameter or return value documentation (even in Swift-only projects), you should now see warnings with suggestions. For example:

warning: Parameter 'smoeValue' not found in function declaration
  --> ../SomeFile.swift:12:7-12:60
9  |
10 | /// - Parameters:
11 + ///   - smoeValue: Some description of this parameter.
   |         ╰─suggestion: Replace 'smoeValue' with 'someValue'
12 | public func doSomething(with someValue: Int) { /* ... */ }

Going forward

We believe that both of these behaviors are useful with very little tradeoffs and hope to enable this by default—with a --disable-parameters-and-returns-validation flag to go back to the previous behavior—sometime in the 6.0 release and refine these behaviors throughout the 6.0 release.

We look forward to hearing how this works in your projects and to talk about refinements to both the processing of language specific parameter and return value documentation as well as warnings about parameter and return value documentation.

8 Likes

This a fantastic enhancement that will make everyone more aware of keeping their parameter documentation up to date!

I wonder if generating the parameter description is correct for error parameters is correct it, in the doSomethingWith:error: example the error is actually of type NSError ** which isn't reflected accurately by the generated text. Additionally it's highly specific to Swift/ObjC interop and may not line up with other FFI schemes.

2 Likes

I'd be very happy to iterate on the "error" parameter text. There are lots of existing examples that can be rephrased into a good generic error description.

I believe that it's common convention in Objective-C to refer to (Something *) as a "something object" which would mean that (Something *)* could be describes as "a pointer to a something object". I've also seen (Something *)* be described as a "something reference" to contrast it with a "something object".

Yes. The behavior of adding a generic description for the error parameter and extending the return value documentation is specifically only for Objective-C counterparts to Swift symbols that throws. It doesn't happen between other languages and it doesn't happen unless the Swift API throws. I added this specific case because it's common and because the parameter and return values behave very predictably and can easily be described generically.

It is possible to customize the error description documentation by manually documenting it. DocC still won't display it for the Swift representation since it doesn't have that parameter in its function signature.

I think the text should mention the words "output parameter" to make it clear that the expected usage is something along the lines of:

NSError *error = nil;
[someClass doSomethingWith:5 error:&error];
if (error) {
  // handle error
}
// Or this other style of usage
[someClass doSomethingWith:5 error:nil];

Maybe something along the lines of:

An output parameter that contains the error that occurred on return. If you are not interested in possible errors pass nil.

Ultimately though, I am not sure that automatically inserting this generic parameter description provides additional value to clients as by definition it is entirely inferred from the declaration.

Given the following method and documentation:

public final class Foo {
    /// Function Description
    /// - Throws: currently it never throws
    @objc public func bar() throws {}
}

How is Throws translated to the Objective-C version? Is the documentation included in the generated error parameter?