dmt
(Dima Galimzianov)
1
I wonder is it possible to reexport a module preserving declarations marked with @_spi?
Suppose we have the following module structure:
ModuleA:
@_spi(_Foo) func foo() {}
ModuleB:
@_exported import ModuleA
ModuleC:
@_spi(_Foo) import ModuleB
foo() // error: 'foo' is inaccessible due to '@_spi' protection level
2 Likes
xymus
(Alexis Laferrière)
2
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.
See: [Sema] Restrict reexported SPIs to modules with an `export_as` relationship by xymus · Pull Request #62102 · apple/swift · GitHub
dmt
(Dima Galimzianov)
3
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.
dmt
(Dima Galimzianov)
4
@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.
Issue
dmt
(Dima Galimzianov)
5
@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?
ModuleA:
@_spi(_Foo) func foo() {}
ModuleB:
@_exported @_spi(_Foo) import ModuleA
// or @_exported @_exported_spi(_Foo) import ModuleA
ModuleC:
@_spi(_Foo) import ModuleB
foo()