Currently SPIs are only reexported when there's also an -export-as declaration from the underlying module. This was the middle ground chosen to avoid unintentionally reexporting SPIs. Be careful as -export-as has a wider effect.
I'd be curious to know what is your use case as we could make something more specific to this need.
I'm fighting the viral effect of extensions. This is one of approaches I think about.
Consider a big library that consists of ModuleA, ModuleB, UmbrellaModule. And a consumer module App.
// ModuleA:
extension Array where Element == Double {
public func sum() -> Double {
reduce(0, +)
}
}
public func someUsefulFunction1(_ x: [Double]) -> Double {
return x.sum()
}
// ModuleB:
import ModuleA // danger point
public func someUsefulFunction2(_ x: [Double]) -> Double {
return x.sum() / x.count // (or whatever what depends on ModuleA)
}
// UmbrellaModule:
@_exported import ModuleA
@_exported import ModuleB
// App:
import UmbrellaModule
[1.0].sum() // ok, but it shouldn't be ok.
As we imported UmbrellaModule in App we got visible:
Free functions someUsefulFunction1 and someUsefulFunction2.
Method sum on Array<Double>.
Free functions are no problem. In case of name clash with functions defined in other module we can easily workaround it by calling them by FQN: UmbrellaModule.someUsefulFunction1().
But extensions are viral: once an extension is visible in the import tree it will be visible up to the root.
There is another option how to prevent pollution from extensions. I could split ModuleA into ModuleA and ModuleAExtensions, and then carefully import ModuleAExtensions as _implementationOnly. But that's much more fragile approach - one missed attribute and we got a pollution.
@xymus I was able to make it work via export_as in the underlying module's modulemap, but not with -exported-as. It feels like the parity is broken here lib/AST/Module.cpp
bool ModuleDecl::isExportedAs(const ModuleDecl *other) const {
auto clangModule = findUnderlyingClangModule();
if (!clangModule)
return false;
return other->getRealName().str() == clangModule->ExportAsModule;
}
I think it should take into account ExportAsName field of ModuleDecl as well.
@xymus Did you consider to reexport SPIs when they're explicitly imported? I.e. you express your intention to reexport by marking them with @_exported @_spi(x) in sources, rather than a modulemap or compiler flag?