Thank you for clarifying. I'm still personally worried about the pairwise module based checking. Modules are a fine abstraction, but I fear actual library mechanics for how and when code is compiled and by whom will make a hash of this all come transitive dependencies.
Autocomplete has historically had high fragility in and around closures, just giving up. As a good deal of the sendable stuff is related to closure params, I worry about a sendable and non-sendable double entry in an autocomplete data structure gumming up closure autocomplete. It can also get a bit frisky around the combination of generics + closures. Besides auto-complete, I worry about the compiler itself getting bothered by these definitions that may conflict.
If we look at only the context of the "checker" and the "checked", I see deep hierarchies coming out possibly still with both sendable and unsafeSendable interfaces about the same types/closure signatures.
AppGamma:
LibB
LibC
LibD
LibF
LibB:
LibD
LibE
LibC
LibC: // This is preconcurrent code
LibE
LibQ
LibF: // This is a library which explicitly tries to work for preConcurreny code and swift 6.
LibE
LibQ
Given the above situation here is where it seems to be trouble. If LibE had closure taking APIs, or even types used in closures a lot, specific ways of packaging and compiling LibB, LibC, LibF and LibE could all result in "duplicate but slightly different" signatures with respect to the Sendable interface. This seems possible given even the same LibE codebase in each.
Like:
let cc = LibC.Cicle()
let fc = LibF.FracturedCircle() // this of type circle
let bc = LibB.BumpyCircle()
let arrayOfCircles = [cc, fc, bc] //I'm pretty sure y'all would catch it if it didn't compile here during development of the feature
//I'm also pretty sure this situation is going to be handled alright
libF.funcFThatRequiresArrayOfCircles(arrayOfCircles)
libC.funcCThatRequiresArrayOfCircles(arrayOfCircles)
libB.funcBThatRequiresArrayOfCircles(arrayOfCircles)
but when we get to stuff more like this:
//in AppGamma
struct Peg<T:Round>{
...
}
SomeVaguelyConcurrencyRelatedApp.doStuff{
fc.checkArea(cc) { peg1
cc.checkArea(fc) { peg2
peg1 == peg2
}
}
}
This is where I greatly fear we hit weird compilation error messages like "Circle is not of type Circle" compiler errors, even if generics aren't involved.
If you take AppGamma and LibC, LibF or LibE are removed, say in a testing situation, or just because it's no longer needed, do the types change at the AppGamma level?
This all seems like a very large testing matrix for the implementation of your proposal, with a lot of guessing at how various providers of Swift libraries could slightly blow up everything. I'm not talking about falsely marking non-concurrent code here; I'm just talking about the code compiling.
Apologies if I really am missing something there, but this seems like a very real world composition problem that I'm not sure the changes to Error + the function/closure attribute + the type inference + compiler flags would all sail through necessarily. I feel it could strand some apps pre-concurrency due to a hairy library slightly doing something wrong.
--
On another note, of sincere feedback about the parts that are confusing iffy feeling, and in the spirit of frank feedback on what I could clearly understand from the document, not second guessing your decisions:
The slight difference in Sendable the protocol and @Sendable functions is also tricky. They maybe feel like they mean something different. Perhaps this is just the "new taste" of marker protocols, and they are really "protocols that act more like closure parameter attributes". Perhaps it's just calling marker protocols "protocols" is the issue, time will tell probably. Not trying to bike shed, but tell what it feels like from the outside.
Like if you look at @noescape vs @escaping, the second one marks code that "isn't safe" to flexibly use however. One of the reasons in the original proposal to assume @noescape was that humans are likely to fail to notice that they made something escape. This allows the compiler to go "hey person, you are not typing right, and this extra thingy can go up here" via a fixit. You can't as easily do that with @sendable or @unsafeSendable or @predatesConcurrency, for instance. If you said why the "no escape" variant is chosen as the default but not in the escaping situation perhaps it would have just all made sense, but I'm not sure.
Is "Sendibility" the right property to be annotating closures/functions with? I don't know.
It's "dual" doesn't have an obviously better name. I am not sure I'd love the name "@nonSendableAllowed", but it's what I mean. Concurrency is pretty hard for humans, and I'd imagine it's even harder for y'all to not only model concurrency, but how we all will fail repeatedly while trying to do it right. Would the dual better focus us on "how we messed up" so we could better write concurrent code? Or specifically having to mark things "@doesNotCareIfConcurrent", "@acceptsJanky"?
@unsafeSendable feels like it might be okay. @preConcurrency may work but isn't super clear on "why we care", while possibly not accurate from the perspective of a person writing a library at some time in the future. Sorry to be both unhelpful on this front and still confused.
@libraryTryingToDoBothConcurrencyAndHandleOldStuffWellSoPleaseGetOfItsBack is a little wordy, but probably closer to what this is all about. It's really weird to deal with varying answers to "Is this library guaranteeing safe, concurrent types", "Is this library trying to use safe, concurrent types" and "Is this library only able to provide safe concurrent types if it is also provided them".
I do not envy you in both having to figure out a technical answer to all of this and then teach us all how and what to think. Best of luck, this feels most of the way there.