Skip to content

[NFC, Incremental] Refactoring to facilitate replacing the inputDependencySourceMap with the right thing #664

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

Merged
merged 4 commits into from
May 27, 2021

Conversation

davidungar
Copy link
Contributor

@davidungar davidungar commented May 14, 2021

A refactoring of inputDependencySourceMap prior to really fixing the below:

See #662

There is a remaining lurking bug here that involves an extra step.
Suppose you build in a normal way with foo.swift/foo.c setup. Then you
rebuild and cancel it after a frontend has emitted a new swiftdeps file,
finally, you crash the driver by exploiting this bug. The driver is now
in a state where swiftdeps disagree with the priors which disagree with
the output file map.

All this to say, we should take the earliest opportunity to remove
BidirectionalMap and its uses. But this commit is not the one to make
such a structural change to the driver.

Start on that.

rdar://77998890

@davidungar
Copy link
Contributor Author

@swift-ci please test macOS Platform

@davidungar davidungar force-pushed the replace-bijection-on-vacation branch 2 times, most recently from c679036 to c2687c8 Compare May 21, 2021 18:11
@davidungar davidungar changed the title [DNM, WIP, Incremental] Replace the inputDependencySourceMap with the right thing [NFC, Incremental] Refactoring to facilitate replacing the inputDependencySourceMap with the right thing May 21, 2021
@davidungar davidungar force-pushed the replace-bijection-on-vacation branch from e7c52e9 to b567bec Compare May 21, 2021 20:07
@davidungar
Copy link
Contributor Author

@swift-ci please test

@davidungar davidungar requested a review from CodaFi May 21, 2021 20:10
inputsAddedSincePriors }
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
_ dependencySource: DependencySource,
`for` _ : AdditionPurpose) {
Copy link
Contributor

@CodaFi CodaFi May 21, 2021

Choose a reason for hiding this comment

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

Nit: You don't need to quote keywords when they're external labels.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, will do.

// MARK: - Populating
extension InputDependencySourceMap {
public enum AdditionPurpose {
case mocking,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please document these. Their names alone don't reveal much at the point of usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okey-doke!


/// Returns: false on error
func populateInputDependencySourceMap(
`for` purpose: InputDependencySourceMap.AdditionPurpose
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Same here. Don't need the quotes around for.

biMap[source]
}

@_spi(Testing) public func enumerateToSerializePriors(
Copy link
Contributor

@CodaFi CodaFi May 21, 2021

Choose a reason for hiding this comment

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

This name conflates the API's purpose with its usage. I think the swift-ier name is something closer to just enumerate(_:) or forEach(_:).

Copy link
Contributor Author

@davidungar davidungar May 21, 2021

Choose a reason for hiding this comment

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

Yes, it does. The conflation is quite deliberate here. The enclosing type is meant to operate at the level of intention. This level distinction paves the way for the greater structural change to come.

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand that, but Swift does not use the name of the API in the same way that Objective-C or Smalltalk might encourage the use of intention-revealing selectors.


// MARK: - Accessing
extension InputDependencySourceMap {
@_spi(Testing) public func getSourceIfKnown(for input: TypedVirtualPath) -> DependencySource? {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just source(for:) would do here.

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, it would. And I'll remove the "get" prefix.

The idea here was to draw a very explicit distinction with ModuleDependencyGraph.getRequiredSource. In an earlier draft, both were named getSource and it was not easy to search for one as opposed to the other, because the only difference in usage was the result type. So, I decided to rename at least one, for the sake of clarity. Then, for the sake of symmetry (and clarity), I renamed them both.
So, I would rather keep the "IfKnown" part.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm... after eliminating the "get"s, the "IfKnown" etc., moves to the keyword. You'll see after I push. Does that look better?

biMap[input]
}

@_spi(Testing) public func getInputIfKnown(for source: DependencySource) -> TypedVirtualPath? {
Copy link
Contributor

Choose a reason for hiding this comment

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

just input(for:) as well

Copy link
Contributor

@CodaFi CodaFi left a comment

Choose a reason for hiding this comment

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

Some documentation of this new interface would be welcome. API notes are in-line.

@davidungar
Copy link
Contributor Author

Some documentation of this new interface would be welcome. API notes are in-line.

Yes, I'll add.

@davidungar
Copy link
Contributor Author

Thanks for the comments; they helped! Have addressed them, and now will fix the conflicts.

@davidungar davidungar force-pushed the replace-bijection-on-vacation branch from 4022e1a to 41237bf Compare May 21, 2021 21:08
@davidungar
Copy link
Contributor Author

@swift-ci please test

@davidungar
Copy link
Contributor Author

Thanks, @CodaFi , for productive comments. I've made the changes requested, modulo keeping some intentionality in the function names. This rev may not be there yet, so please let me know what you see.

///
/// - Parameter source: A `DependencySource` (containing a swiftdeps file)
/// - Returns: the corresponding input source file, or nil if none known.
@_spi(Testing) public func input(ifKnownFor source: DependencySource) -> TypedVirtualPath? {
Copy link
Contributor

Choose a reason for hiding this comment

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

ifKnownFor has the same issue as below. Just for suffices.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please see my comment for the next issue. I have a rule: Correlate salience with importance in order to ease the comprehension for new maintainer.

@@ -121,7 +121,8 @@ extension ModuleDependencyGraph.Tracer {
path.compactMap { node in
node.dependencySource.map {
source in
graph.inputDependencySourceMap[source].map { input in
graph.inputDependencySourceMap.input(ifKnownFor: source).map {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason this is here? Seems superfluous.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The "ifKnownFor" is there because it makes two aspects more salient:

  1. That the function might not return a value. (In this use, it reinforces that the map is to handle an optional, rather than a collection.), and
  2. That it depends on if the mapping is "known". I'd be happy to find a better word than "known". "Has been seen" feels too wordy.

/// - Parameter why: the purpose for this addition. Will be used for future restructuring.
@_spi(Testing) public mutating func addEntry(_ input: TypedVirtualPath,
_ dependencySource: DependencySource,
for why: AdditionPurpose) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the for parameter is dead. Is that intentional?

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. As the documentation says, it will be used in the future. For now, it makes clear at the point-of-use what the purpose is.

}
}
extension OutputFileMap {
fileprivate func getDependencySource(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit from before: get prefixes usually imply indirect return values in Cocoa and Swift. Just dependencySource(for:diagnosticEngine:) suffices.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks--done!

if let source = ofm.getDependencySource(for: input, diagnosticEngine: diags) {
inputDependencySourceMap.addEntry(input, source, for: purpose)
}
else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: OTBS here please

Suggested change
else {
if let source = ofm.getDependencySource(for: input, diagnosticEngine: diags) {
inputDependencySourceMap.addEntry(input, source, for: purpose)
} else {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did a look-up on "OTBS" and it came back with "off track horse betting"! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like the request is to put the else on the same line as the closing brace--no prob!

file: String = #file,
line: Int = #line) -> DependencySource {
guard let source = inputDependencySourceMap[input] else {
@_spi(Testing) public func source(requiredFor input: TypedVirtualPath,
Copy link
Contributor

@CodaFi CodaFi May 25, 2021

Choose a reason for hiding this comment

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

The requirement part of this API is communicated by the type signature - being that it has a non-optional return value. You could reinforce this in documentation, but I think for suffices.

Copy link
Contributor Author

@davidungar davidungar May 25, 2021

Choose a reason for hiding this comment

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

The requirement is in the function name, because it makes the required nature more salient at the point of use, without clicking to see the documentation, or the type of the assigned-to variable.
Consider: let foo = source(requiredFor: someInput) vs let foo = source(for: someInput).
The first construction makes the required nature of the mapping more salient.

I have a rule: that when some distinction is important, I make it salient at the point of use, and the correlation between importance and salience trumps most other considerations.

@@ -329,7 +345,8 @@ extension ModuleDependencyGraph {
) -> TransitivelyInvalidatedInputSet? {
var invalidatedInputs = TransitivelyInvalidatedInputSet()
for invalidatedSwiftDeps in collectSwiftDepsUsingInvalidated(nodes: directlyInvalidatedNodes) {
guard let invalidatedInput = getInput(for: invalidatedSwiftDeps) else {
guard let invalidatedInput = input(neededFor: invalidatedSwiftDeps)
else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Put this back if you could. This fits onto 80 columns just fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the rule you follow in general for the "else" in a guard? 80-columns?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

@davidungar
Copy link
Contributor Author

@CodaFi Thank you for your review. I have responded to your comments and made changes where I agree. You have a wealth of experience with Swift style, and that helps my code fit in. To clarify, I think we agree that this PR makes no functional change, and that (for me) is the most critical part of your review, because I don't want to break the driver. There are some remaining disagreements on function naming, but I think they follow from my practice of choosing salience at the point-of-use. If Xcode were better at showing inferred types, or Doxygen documentation, I'd be in agreement with you.

Would you be able to approve this PR at this point?

@davidungar
Copy link
Contributor Author

@swift-ci please test

Copy link
Contributor

@CodaFi CodaFi left a comment

Choose a reason for hiding this comment

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

I remain unconvinced that this is a meaningful change, and moreover that the new API surface here is an improvement over the status quo. But this PR has also been blocked on my review and I don’t see a reason for that much, so

@davidungar
Copy link
Contributor Author

I remain unconvinced that this is a meaningful change, and moreover that the new API surface here is an improvement over the status quo. But this PR has also been blocked on my review and I don’t see a reason for that much, so

I understand. Thank you so very much for compromising here.

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.

2 participants