Skip to content

[Sema] Treat @usableFromInline package decls from interface as public and skip access checks #75745

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 2 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,14 @@ class ValueDecl : public Decl {
/// \c \@usableFromInline, \c \@inlinalbe, and \c \@_alwaysEmitIntoClient
bool isUsableFromInline() const;

/// Treat as public and allow skipping access checks if the following conditions
/// are met:
/// - This decl has a package access level,
/// - Has a @usableFromInline (or other inlinable) attribute,
/// - And is defined in a module built from a public or private
/// interface that does not contain package-name.
bool isInterfacePackageEffectivelyPublic() const;

/// Returns \c true if this declaration is *not* intended to be used directly
/// by application developers despite the visibility.
bool shouldHideFromEditor() const;
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ def disable_bridging_pch : Flag<["-"], "disable-bridging-pch">,

def disable_print_package_name_for_non_package_interface :
Flag<["-"], "disable-print-package-name-for-non-package-interface">,
Flags<[FrontendOption, NoDriverOption, ModuleInterfaceOption, HelpHidden]>,
Flags<[FrontendOption, NoDriverOption, HelpHidden]>,
HelpText<"Disable adding package name to public or private interface">;

def lto : Joined<["-"], "lto=">,
Expand Down
3 changes: 2 additions & 1 deletion lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,8 @@ class Verifier : public ASTWalker {
PrettyStackTraceDecl debugStack("verifying access", D);
if (!D->getASTContext().isAccessControlDisabled()) {
if (D->getFormalAccessScope().isPublic() &&
D->getFormalAccess() < AccessLevel::Public) {
D->getFormalAccess() < AccessLevel::Public &&
!D->isInterfacePackageEffectivelyPublic()) {
Out << "non-public decl has no formal access scope\n";
D->dump(Out);
abort();
Expand Down
23 changes: 23 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4212,6 +4212,21 @@ bool ValueDecl::isUsableFromInline() const {
return false;
}

bool ValueDecl::isInterfacePackageEffectivelyPublic() const {
// If a package decl has a @usableFromInline (or other inlinable)
// attribute, and is defined in a module built from interface, it
// can be referenced by another module that imports it even though
// the defining interface module does not have package-name (such
// as public or private interface); in such case, the decl is treated
// as public and access checks in sema are skipped.
// We might need to add another check here to ensure the interface
// was part of the same package before the package-name was removed.
return getFormalAccess() == AccessLevel::Package &&
isUsableFromInline() &&
getModuleContext()->getPackageName().empty() &&
getModuleContext()->isBuiltFromInterface();
}

bool ValueDecl::shouldHideFromEditor() const {
// Hide private stdlib declarations.
if (isPrivateStdlibDecl(/*treatNonBuiltinProtocolsAsPublic*/ false) ||
Expand Down Expand Up @@ -4304,6 +4319,9 @@ static AccessLevel getAdjustedFormalAccess(const ValueDecl *VD,
if (useDC && VD->getASTContext().isAccessControlDisabled())
return getMaximallyOpenAccessFor(VD);

if (VD->isInterfacePackageEffectivelyPublic())
return AccessLevel::Public;

if (treatUsableFromInlineAsPublic &&
access < AccessLevel::Public &&
VD->isUsableFromInline()) {
Expand Down Expand Up @@ -4486,6 +4504,8 @@ getAccessScopeForFormalAccess(const ValueDecl *VD,
case AccessLevel::Package: {
auto pkg = resultDC->getPackageContext(/*lookupIfNotCurrent*/ true);
if (!pkg) {
if (VD->isInterfacePackageEffectivelyPublic())
return AccessScope::getPublic();
// Instead of reporting and failing early, return the scope of resultDC to
// allow continuation (should still non-zero exit later if in script mode)
return AccessScope(resultDC);
Expand Down Expand Up @@ -4621,6 +4641,9 @@ static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
if (VD->getASTContext().isAccessControlDisabled())
return true;

if (VD->isInterfacePackageEffectivelyPublic())
return true;

auto access = getAccessLevel();
auto *sourceDC = VD->getDeclContext();

Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4277,6 +4277,9 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
requiredAccessScope.requiredAccessForDiagnostics();
auto proto = conformance->getProtocol();
auto protoAccessScope = proto->getFormalAccessScope(DC);
if (proto->isInterfacePackageEffectivelyPublic())
return;

bool protoForcesAccess =
requiredAccessScope.hasEqualDeclContextWith(protoAccessScope);
auto diagKind = protoForcesAccess
Expand Down
13 changes: 9 additions & 4 deletions test/ModuleInterface/load_package_interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// RUN: %target-swift-frontend -emit-module %t/Bar.swift -I %t \
// RUN: -module-name Bar -package-name foopkg -package-name barpkg \
// RUN: -enable-library-evolution -swift-version 5 \
// RUN: -disable-print-package-name-for-non-package-interface \
// RUN: -emit-module-interface-path %t/Bar.swiftinterface \
// RUN: -emit-private-module-interface-path %t/Bar.private.swiftinterface \
// RUN: -emit-package-module-interface-path %t/Bar.package.swiftinterface
Expand All @@ -31,7 +32,8 @@
// RUN: -package-name barpkg \
// RUN: -Rmodule-loading 2> %t/load-pkg-off.output
// RUN: %FileCheck -check-prefix=CHECK-LOAD-PKG-OFF %s < %t/load-pkg-off.output
// CHECK-LOAD-PKG-OFF: error: module 'Bar' is in package 'barpkg' but was built from a non-package interface; modules of the same package can only be loaded if built from source or package interface:{{.*}}Bar.private.swiftinterface
// CHECK-LOAD-PKG-OFF: remark: loaded module 'Bar'; source: '{{.*}}Bar.private.swiftinterface', loaded: '{{.*}}Bar-{{.*}}.swiftmodule'
// CHECK-LOAD-PKG-OFF: error: cannot find 'PkgKlass' in scope; did you mean 'PubKlass'?

/// Client loads a private interface since the package-name is different from the loaded module's.
// RUN: not %target-swift-frontend -typecheck %t/Client.swift -I %t \
Expand All @@ -45,13 +47,15 @@
// RUN: rm -rf %t/*.swiftmodule
// RUN: rm -rf %t/Bar.package.swiftinterface

/// Client loads a private interface since package interface doesn't exist. It should error since the loaded module is not built from a package interface.
/// Client loads a private interface since package interface doesn't exist. It should error since the
/// loaded module is not built from a package interface.
// RUN: not %target-swift-frontend -typecheck %t/Client.swift -I %t \
// RUN: -package-name barpkg \
// RUN: -experimental-package-interface-load \
// RUN: -Rmodule-loading 2> %t/load-priv.output
// RUN: %FileCheck -check-prefix=CHECK-LOAD-PRIV %s < %t/load-priv.output
// CHECK-LOAD-PRIV: no such module 'Bar'
// CHECK-LOAD-PRIV: remark: loaded module 'Bar'; source: '{{.*}}Bar.private.swiftinterface', loaded: '{{.*}}Bar-{{.*}}.swiftmodule'
// CHECK-LOAD-PRIV: error: cannot find 'PkgKlass' in scope; did you mean 'PubKlass'?

// RUN: rm -rf %t/*.swiftmodule
// RUN: rm -rf %t/Bar.private.swiftinterface
Expand All @@ -64,7 +68,8 @@
// RUN: -Rmodule-loading 2> %t/load-pub.output

// RUN: %FileCheck -check-prefix=CHECK-LOAD-PUB %s < %t/load-pub.output
// CHECK-LOAD-PUB: no such module 'Bar'
// CHECK-LOAD-PUB: remark: loaded module 'Bar'; source: '{{.*}}Bar.swiftinterface', loaded: '{{.*}}Bar-{{.*}}.swiftmodule'
// CHECK-LOAD-PUB: error: cannot find 'PkgKlass' in scope; did you mean 'PubKlass'?

//--- Bar.swift
public class PubKlass {
Expand Down
53 changes: 48 additions & 5 deletions test/ModuleInterface/package_interface_disable_package_name.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

/// Do not print package-name for public or private interfaces
// RUN: %target-build-swift -emit-module %s -I %t \
// RUN: %target-build-swift -emit-module %t/Bar.swift -I %t \
// RUN: -module-name Bar -package-name foopkg \
// RUN: -enable-library-evolution -swift-version 6 \
// RUN: -package-name barpkg \
Expand All @@ -20,24 +21,31 @@
// CHECK-PRIVATE-NOT: -package-name barpkg
// CHECK-PACKAGE-NOT: -package-name foopkg

// CHECK-PUBLIC: -enable-library-evolution -swift-version 6 -disable-print-package-name-for-non-package-interface -module-name Bar
// CHECK-PRIVATE: -enable-library-evolution -swift-version 6 -disable-print-package-name-for-non-package-interface -module-name Bar
// CHECK-PACKAGE: -enable-library-evolution -swift-version 6 -disable-print-package-name-for-non-package-interface -module-name Bar -package-name barpkg
// CHECK-PUBLIC: -enable-library-evolution -swift-version 6 -module-name Bar
// CHECK-PRIVATE: -enable-library-evolution -swift-version 6 -module-name Bar
// CHECK-PACKAGE: -enable-library-evolution -swift-version 6 -module-name Bar -package-name barpkg

/// Verify building modules from non-package interfaces succeeds without the package-name flag.
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -verify
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -package-name barpkg -verify

// RUN: rm -rf %t/Bar.swiftmodule
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.private.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -verify
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -package-name barpkg -verify

// RUN: rm -rf %t/Bar.swiftmodule
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.package.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -package-name barpkg -experimental-package-interface-load -verify

// RUN: rm -rf %t/Bar.swiftmodule
// RUN: rm -rf %t/Bar.swiftinterface
// RUN: rm -rf %t/Bar.private.swiftinterface
// RUN: rm -rf %t/Bar.package.swiftinterface

/// By default, -package-name is printed in all interfaces.
// RUN: %target-build-swift -emit-module %s -I %t \
// RUN: %target-build-swift -emit-module %t/Bar.swift -I %t \
// RUN: -module-name Bar -package-name barpkg \
// RUN: -enable-library-evolution -swift-version 6 \
// RUN: -emit-module-interface-path %t/Bar.swiftinterface \
Expand All @@ -52,11 +60,46 @@

/// Building modules from non-package interfaces with package-name (default mode) should succeed.
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/ExpectFail.swift -I %t -package-name barpkg -verify

// RUN: rm -rf %t/Bar.swiftmodule
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.private.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/ExpectFail.swift -I %t -package-name barpkg -verify

// RUN: rm -rf %t/Bar.swiftmodule
// RUN: %target-swift-frontend -compile-module-from-interface %t/Bar.package.swiftinterface -o %t/Bar.swiftmodule -module-name Bar
// RUN: %target-swift-frontend -typecheck %t/Use.swift -I %t -package-name barpkg -experimental-package-interface-load -verify

//--- Bar.swift
public struct PubStruct {}
@_spi(bar) public struct SPIStruct {}
package struct PkgStruct {}

@usableFromInline
package struct UfiPkgStruct {
@usableFromInline
package var ufiPkgVar: PubStruct

package var pkgVar: PubStruct

@usableFromInline
package init(_ x: PubStruct, _ y: PubStruct) {
ufiPkgVar = x
pkgVar = y
}
}


//--- Use.swift
import Bar

func use(_ arg: PubStruct) -> PubStruct {
return UfiPkgStruct(arg, arg).ufiPkgVar
}

//--- ExpectFail.swift
import Bar // expected-error {{module 'Bar' is in package 'barpkg' but was built from a non-package interface; modules of the same package can only be loaded if built from source or package interface}}

func use(_ arg: PubStruct) -> PubStruct {
return UfiPkgStruct(arg, arg).ufiPkgVar
}
23 changes: 11 additions & 12 deletions test/Sema/accessibility_package_import_interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@
// RUN: -module-name Dep -swift-version 5 -I %t \
// RUN: -package-name myPkg \
// RUN: -enable-library-evolution \
// RUN: -disable-print-package-name-for-non-package-interface \
// RUN: -emit-module-path %t/Dep.swiftmodule \
// RUN: -emit-module-interface-path %t/Dep.swiftinterface \
// RUN: -emit-private-module-interface-path %t/Dep.private.swiftinterface

// TEST Dep private interface should contain the package name
// RUN: %target-swift-typecheck-module-from-interface(%t/Dep.private.swiftinterface) -module-name Dep -I %t
// RUN: %FileCheck %s --check-prefix=CHECK-DEP-PRIVATE < %t/Dep.private.swiftinterface
// CHECK-DEP-PRIVATE: -package-name myPkg

// TEST Dep.swiftmodule should contain package name and package symbols
// RUN: llvm-bcanalyzer --dump %t/Dep.swiftmodule | %FileCheck %s --check-prefix=CHECK-DEP-BC
// CHECK-DEP-BC: <MODULE_PACKAGE_NAME abbrevid=6/> blob data = 'myPkg'
// CHECK-DEP-BC: <MODULE_PACKAGE_NAME {{.*}}> blob data = 'myPkg'

// TEST Lib should load Dep.swiftmodule and access package decls if in the same package and error if not
// RUN: %target-swift-frontend -typecheck %t/Lib.swift -package-name myPkg -I %t
Expand All @@ -39,9 +35,9 @@
// RUN: -module-name Dep -I %t \
// RUN: -o %t/Dep.swiftmodule

// TEST Dep binary built from interface should contain package name but no package symbols
// TEST Dep binary built from interface should not contain package name or package symbols.
// RUN: llvm-bcanalyzer --dump %t/Dep.swiftmodule | %FileCheck %s --check-prefix=CHECK-DEP-INTER-BC
// CHECK-DEP-INTER-BC: <MODULE_PACKAGE_NAME abbrevid=7/> blob data = 'myPkg'
// CHECK-DEP-INTER-BC-NOT: <MODULE_PACKAGE_NAME {{.*}}> blob data = 'myPkg'

// TEST Lib should error on loading Dep built from interface and accessing package symbols (unless usableFromInline or inlinable)
// RUN: %target-swift-frontend -typecheck %t/Lib.swift -package-name myPkg -I %t -verify
Expand All @@ -59,6 +55,7 @@
// RUN: -module-name LibPass -swift-version 5 -I %t \
// RUN: -package-name myPkg \
// RUN: -enable-library-evolution \
// RUN: -disable-print-package-name-for-non-package-interface \
// RUN: -emit-module-path %t/LibPass.swiftmodule \
// RUN: -emit-module-interface-path %t/LibPass.swiftinterface \
// RUN: -emit-private-module-interface-path %t/LibPass.private.swiftinterface
Expand Down Expand Up @@ -86,8 +83,11 @@
@usableFromInline
package class PackageKlassUFI {
@usableFromInline package init() {}
@usableFromInline package var packageVarUFI: String = "pkgUFI"
package var packageVar: String = "pkg"

@usableFromInline package var packageVarUFI: String = "pkgUFI"
// expected-note@-1 * {{'packageVarUFI' declared here}}
// expected-error@-1 * {{'packageVarUFI' declared here}}
}

package func packageFunc() {
Expand All @@ -109,7 +109,7 @@ public func publicFuncInlinable() {
}

//--- Lib.swift
import Dep // expected-error {{module 'Dep' is in package 'myPkg' but was built from a non-package interface; modules of the same package can only be loaded if built from source or package interface}}
import Dep

public func libFunc() {
publicFuncInlinable()
Expand All @@ -118,8 +118,7 @@ public func libFunc() {
packageFunc() // expected-error {{cannot find 'packageFunc' in scope}}
let x = PackageKlassUFI()
let y = x.packageVarUFI
let z = x.packageVar // expected-error {{value of type 'PackageKlassUFI' has no member 'packageVar'}}
print(x, y, z)
print(x, y)
}


Expand Down
8 changes: 4 additions & 4 deletions test/Sema/accessibility_package_inline_interface.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// RUN: %empty-directory(%t)
// RUN: %{python} %utils/split_file.py -o %t %s
// RUN: split-file %s %t

// RUN: %target-swift-frontend -module-name Utils1 %t/Utils.swift -emit-module -emit-module-path %t/Utils1.swiftmodule -package-name myLib -swift-version 5
// RUN: test -f %t/Utils1.swiftmodule

// RUN: %target-swift-frontend -module-name Utils %t/Utils.swift -emit-module -emit-module-interface-path %t/Utils.swiftinterface -package-name myLib -enable-library-evolution -swift-version 5
// RUN: %target-swift-frontend -module-name Utils %t/Utils.swift -emit-module -emit-module-interface-path %t/Utils.swiftinterface -package-name myLib -enable-library-evolution -swift-version 5 -disable-print-package-name-for-non-package-interface

// RUN: test -f %t/Utils.swiftinterface
// RUN: %target-swift-typecheck-module-from-interface(%t/Utils.swiftinterface) -I%t

// RUN: %FileCheck %s -check-prefix CHECK-UTILS < %t/Utils.swiftinterface
// CHECK-UTILS: -module-name Utils
// CHECK-UTILS: -package-name myLib
// CHECK-UTILS: @usableFromInline
// CHECK-UTILS: package class PackageKlassProto {
// CHECK-UTILS: @usableFromInline
Expand Down Expand Up @@ -70,7 +70,7 @@
// CHECK-UTILS: }


// BEGIN Utils.swift
//--- Utils.swift
package protocol PackageProto {
var pkgVar: Double { get set }
func pkgFunc()
Expand Down
12 changes: 5 additions & 7 deletions test/Sema/accessibility_package_interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// RUN: -module-name Utils -swift-version 5 -I %t \
// RUN: -package-name swift-utils.log \
// RUN: -enable-library-evolution \
// RUN: -emit-module-path %t/Utils.swiftmodule \
// RUN: -disable-print-package-name-for-non-package-interface \
// RUN: -emit-module-interface-path %t/Utils.swiftinterface \
// RUN: -emit-private-module-interface-path %t/Utils.private.swiftinterface

Expand All @@ -18,7 +18,6 @@
// CHECK-PUBLIC-UTILS-NOT: package class PackageKlass
// CHECK-PUBLIC-UTILS-NOT: package var pkgVar
// CHECK-PUBLIC-UTILS: -module-name Utils
// CHECK-PUBLIC-UTILS: -package-name swift-utils.log
// CHECK-PUBLIC-UTILS: public func publicFunc()
// CHECK-PUBLIC-UTILS: @usableFromInline
// CHECK-PUBLIC-UTILS: package func ufiPackageFunc()
Expand All @@ -39,7 +38,6 @@
// CHECK-PRIVATE-UTILS-NOT: package class PackageKlass
// CHECK-PRIVATE-UTILS-NOT: package var pkgVar
// CHECK-PRIVATE-UTILS: -module-name Utils
// CHECK-PRIVATE-UTILS: -package-name swift-utils.log
// CHECK-PRIVATE-UTILS: public func publicFunc()
// CHECK-PRIVATE-UTILS: @usableFromInline
// CHECK-PRIVATE-UTILS: package func ufiPackageFunc()
Expand All @@ -51,20 +49,19 @@
// CHECK-PRIVATE-UTILS: @usableFromInline
// CHECK-PRIVATE-UTILS: package var ufiPkgVar

// RUN: %target-swift-frontend -compile-module-from-interface %t/Utils.private.swiftinterface -module-name Utils -I %t -o %t/Utils.swiftmodule

// RUN: %target-swift-frontend -emit-module %t/Client.swift \
// RUN: -module-name Client -swift-version 5 -I %t \
// RUN: -package-name swift-utils.log \
// RUN: -enable-library-evolution \
// RUN: -disable-print-package-name-for-non-package-interface \
// RUN: -emit-module-path %t/Client.swiftmodule \
// RUN: -emit-module-interface-path %t/Client.swiftinterface \
// RUN: -emit-private-module-interface-path %t/Client.private.swiftinterface

// RUN: rm -rf %t/Utils.swiftmodule
// RUN: rm -rf %t/Client.swiftmodule

// RUN: %target-swift-typecheck-module-from-interface(%t/Client.swiftinterface) -I %t -verify
// RUN: %FileCheck %s --check-prefix=CHECK-PUBLIC-CLIENT < %t/Client.swiftinterface
// CHECK-PUBLIC-CLIENT: -package-name swift-utils.log
// CHECK-PUBLIC-CLIENT: @inlinable public func clientFunc()
// CHECK-PUBLIC-CLIENT: publicFunc()
// CHECK-PUBLIC-CLIENT: ufiPackageFunc()
Expand Down Expand Up @@ -113,6 +110,7 @@ import Utils
@inlinable public func clientFunc() -> String {
publicFunc()
ufiPackageFunc()
// return UfiPackageKlass().ufiPkgVar
let u = UfiPackageKlass()
return u.ufiPkgVar
}
Expand Down
Loading