Skip to content
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

Create LinkDestinationSummary elements with multi-language variant content for symbols #60

Merged
merged 9 commits into from Jan 22, 2022

Conversation

d-ronnqvist
Copy link
Contributor

Bug/issue #, if applicable: SR-15574 rdar://86298116

Summary

This change creates LinkDestinationSummary elements with multi-language variant content for symbols. Before this change only the main summary element was created without its variant content.

Dependencies

n/a

Testing

Convert a multi-language documentation catalog and include the --emit-digest flag so that Swift-DocC will create the linkable-entities.json file.

(For testing purposes it may be convenient to also set the DOCC_JSON_PRETTYPRINT environment to YES to get output that's more easily inspectable.)

The content of the linkable-entities.json file should include the variant content of each interface language for each symbol "entity". Each variant should contain only the information that's different in the variant compared to the summarized element.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran the ./bin/test script and it succeeded
  • Updated documentation if necessary

@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

1 similar comment
@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

Copy link
Contributor

@ethan-kusters ethan-kusters left a comment

Choose a reason for hiding this comment

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

Looks great! Just left a few questions.

// Precompute the summarized elements information so that variants can compare their information against it and remove redundant duplicate information.

// Multi-language symbols need to access the default content via the variant accessors (rdar://86580516)
let kind = DocumentationNode.kind(forKind: (symbol.kindVariants[summaryTrait] ?? symbol.kindVariants[.fallback] ?? symbol.kind).identifier)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think these chained ?? checks might be redundant.

The DocumentationDataVariants subscript looks like this:

public subscript(trait: DocumentationDataVariantsTrait) -> Variant? {
    get { values[trait] ?? defaultVariantValue }
    set {
        if trait == .fallback {
            defaultVariantValue = newValue
        } else {
            values[trait] = newValue
        }
    }
}

So the .fallback value is the defaultVariantValue and we always fallback to the defaultVariantValue if the given trait doesn't exist. So that should handle the ?? symbol.kindVariants[.fallback] part inherently.

And the base symbol.kind value is already a wrapper around the kindVariants so I don't think we should be explicitly falling back to it.

We could do an explicit force unwrap of kindVariants.first! which I think is what symbol.kind is doing.

public var kind: SymbolGraph.Symbol.Kind { kindVariants.firstValue! }

In general I think we should probably try to avoid using the non-variant types on Symbol since we should deprecate them soon and they're a little confusing.

Does that make sense or have I missed a case here?

Suggested change
let kind = DocumentationNode.kind(forKind: (symbol.kindVariants[summaryTrait] ?? symbol.kindVariants[.fallback] ?? symbol.kind).identifier)
let kind = DocumentationNode.kind(forKind: (symbol.kindVariants[summaryTrait] ?? symbol.kindVariants.first!).identifier)

I don't love how we're having to force unwrap kindVariants.first here, it seems like defaultVariantValue should automatically fall back to the first variant and be non-optional if we want to guarantee that a variant collection always has a single value. Maybe @franklinsch wants to chime in here.

Copy link
Contributor

Choose a reason for hiding this comment

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

(I think this comment applies to several places in this function but I think we can isolate the discussion here)

Copy link
Member

Choose a reason for hiding this comment

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

I think force-unwrapping first is correct. This value is never expected to be nil in this case, because the kind property always had a value before (non-optional)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the behavior is the same, I would prefer to call symbol.kind (and symbol.title, etc.) so that this code behaves consistently with how those properties are computed (in case that ever changes). That also helps avoid mistakes with force-unwrapping first when the symbol convenience property is optional.


let abstractVariant: Abstract?? = symbol.abstractVariants[trait].map { renderSymbolAbstract($0) }

func nilIfSame<Value: Equatable>(main: Value, variant: Value?) -> Value? {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit:

Suggested change
func nilIfSame<Value: Equatable>(main: Value, variant: Value?) -> Value? {
func nilIfEqual<Value: Equatable>(main: Value, variant: Value?) -> Value? {

XCTAssertEqual(summary.availableLanguages.sorted(by: \.id), [.objectiveC, .swift])
XCTAssertEqual(summary.platforms, renderNode.metadata.platforms)
XCTAssertEqual(summary.usr, "c:objc(cs)Bar")
//
Copy link
Contributor

Choose a reason for hiding this comment

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

Stray open comment delimiter here

@@ -34,3 +34,24 @@ public enum VariantPatchOperation<Value: Codable> {
}
}
}

extension VariantCollection.Variant where Value: RangeReplaceableCollection {
/// Applies the variant's patch operations o a given value and returns the patched value
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Applies the variant's patch operations o a given value and returns the patched value
/// Applies the variant's patch operations to a given value and returns the patched value.

}
return [LinkDestinationSummary(documentationNode: self, path: presentationURL.path, taskGroups: taskGroups, platforms: platforms, compiler: &compiler)] + landmarkSummaries
return [LinkDestinationSummary(documentationNode: self, path: presentationURL.path, taskGroups: taskGroups, variantTaskGroups: variantTaskGroups, platforms: platforms, compiler: &compiler)] + landmarkSummaries
Copy link
Member

Choose a reason for hiding this comment

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

Nit: variantTaskGroups -> taskGroupVariants to align with the other variant APIs.

let taskGroups: [LinkDestinationSummary.TaskGroup]
switch kind {
case .tutorial, .tutorialArticle, .technology, .technologyOverview, .chapter, .volume, .onPageLandmark:
taskGroups = [.init(title: nil, identifiers: context.children(of: reference).map { $0.reference.absoluteString })]
default:
taskGroups = renderNode.topicSections.map { group in .init(title: group.title, identifiers: group.identifiers) }
for variant in renderNode.topicSectionsVariants.variants {
variantTaskGroups[variant.traits] = variant.applyingPatchTo(renderNode.topicSections).map { group in .init(title: group.title, identifiers: group.identifiers) }
Copy link
Member

Choose a reason for hiding this comment

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

Instead of implementing applyingPatchTo, which partly overlaps with JSONPatchApplier's functionality, we can instead use the RenderNodeVariantOverridesApplier API that applies the patch onto the entire render node. However, this will require first encoding the render node to JSON, which is probably undesirable for performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think that will have a performance impact. When linkable entities are emits, this code runs for every page and landmark that have topic section variants. In the worst case this could end up encoding all the pages.

// Precompute the summarized elements information so that variants can compare their information against it and remove redundant duplicate information.

// Multi-language symbols need to access the default content via the variant accessors (rdar://86580516)
let kind = DocumentationNode.kind(forKind: (symbol.kindVariants[summaryTrait] ?? symbol.kindVariants[.fallback] ?? symbol.kind).identifier)
Copy link
Member

Choose a reason for hiding this comment

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

I think force-unwrapping first is correct. This value is never expected to be nil in this case, because the kind property always had a value before (non-optional)

 - Remove redundant retrieval of `.fallback` trait values
 - Rename inline helper function to `nilIfEqual`
 - Rename internal argument to `taskGroupVariants` for consistency
 - Fix typo in documentation comment
@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

Copy link
Contributor

@ethan-kusters ethan-kusters left a comment

Choose a reason for hiding this comment

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

Thanks David!

@d-ronnqvist
Copy link
Contributor Author

@swift-ci please test

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.

None yet

3 participants