Skip to content

Sema: Fix opaque type accessors with inactive availability conditions #77519

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
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
4 changes: 2 additions & 2 deletions lib/IRGen/GenMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,7 @@ namespace {

SmallVector<llvm::BasicBlock *, 4> conditionalTypes;

// Pre-allocate a basic block per condition, so there it's
// Pre-allocate a basic block per condition, so that it's
// possible to jump between conditions.
for (unsigned index : indices(substitutionSet)) {
conditionalTypes.push_back(
Expand Down Expand Up @@ -2619,7 +2619,7 @@ namespace {
auto universal = substitutionSet.back();

assert(universal->getAvailability().size() == 1 &&
universal->getAvailability()[0].first.isEmpty());
universal->getAvailability()[0].first.isAll());

IGF.Builder.CreateRet(
getResultValue(IGF, genericEnv, universal->getSubstitutions()));
Expand Down
38 changes: 29 additions & 9 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3468,7 +3468,6 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {
}

// Check whether all of the underlying type candidates match up.
// TODO [OPAQUE SUPPORT]: diagnose multiple opaque types

// There is a single unique signature, which means that all returns
// matched.
Expand Down Expand Up @@ -3595,6 +3594,7 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {

SmallVector<OpaqueTypeDecl::ConditionallyAvailableSubstitutions *, 4>
conditionalSubstitutions;
SubstitutionMap universalSubstMap = std::get<1>(universallyAvailable);

for (const auto &entry : Candidates) {
auto availabilityContext = entry.first;
Expand All @@ -3603,25 +3603,46 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {
if (!availabilityContext)
continue;

unsigned neverAvailableCount = 0, alwaysAvailableCount = 0;
SmallVector<AvailabilityCondition, 4> conditions;

for (const auto &elt : availabilityContext->getCond()) {
auto condition = elt.getAvailability();

auto availabilityRange = condition->getAvailableRange();
// If there is no lower endpoint it means that the
// current platform is unrelated to this condition
// and we can ignore it.
if (!availabilityRange.hasLowerEndpoint())
// If there is no lower endpoint it means that the condition has no
// OS version specification that matches the target platform.
if (!availabilityRange.hasLowerEndpoint()) {
// An inactive #unavailable condition trivially evaluates to false.
if (condition->isUnavailability()) {
neverAvailableCount++;
continue;
}

// An inactive #available condition trivially evaluates to true.
alwaysAvailableCount++;
continue;
}

conditions.push_back(
{availabilityRange, condition->isUnavailability()});
}

if (conditions.empty())
// If there were any conditions that were always false, then this
// candidate is unreachable at runtime.
if (neverAvailableCount > 0)
continue;

// If all the conditions were trivially true, then this candidate is
// effectively a universally available candidate and the rest of the
// candidates should be ignored since they are unreachable.
if (alwaysAvailableCount == availabilityContext->getCond().size()) {
universalSubstMap = std::get<1>(candidate);
break;
}

ASSERT(conditions.size() > 0);

conditionalSubstitutions.push_back(
OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get(
Ctx, conditions,
Expand All @@ -3631,9 +3652,8 @@ class OpaqueUnderlyingTypeChecker : public ASTWalker {
// Add universally available choice as the last one.
conditionalSubstitutions.push_back(
OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get(
Ctx, {{VersionRange::empty(), /*unavailable=*/false}},
std::get<1>(universallyAvailable)
.mapReplacementTypesOutOfContext()));
Ctx, {{VersionRange::all(), /*unavailable=*/false}},
universalSubstMap.mapReplacementTypesOutOfContext()));

OpaqueDecl->setConditionallyAvailableSubstitutions(
conditionalSubstitutions);
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4559,7 +4559,7 @@ class DeclDeserializer {
} else {
limitedAvailability.push_back(
OpaqueTypeDecl::ConditionallyAvailableSubstitutions::get(
ctx, {{VersionRange::empty(), /*unavailability=*/false}},
ctx, {{VersionRange::all(), /*unavailability=*/false}},
subMapOrError.get()));

opaqueDecl->setConditionallyAvailableSubstitutions(limitedAvailability);
Expand Down
91 changes: 91 additions & 0 deletions test/IRGen/opaque_result_with_conditional_availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ struct C : P {
func hello() { print("Hello from C") }
}

@available(iOS 100, *)
struct D : P {
func hello() { print("Hello from D") }
}

func test_multiple_single() -> some P {
if #available(macOS 100.0.1, *) {
return A()
Expand Down Expand Up @@ -61,6 +66,44 @@ func test_multiple_single() -> some P {
// CHECK-NEXT: ret ptr @"$s43opaque_result_with_conditional_availability1CVAA1PAAWP"
// CHECK-NEXT: }

func test_multiple_single_multiplatform() -> some P {
if #available(macOS 100.0.1, *), #available(iOS 100, *) {
return A()
}

return C()
}

// CHECK: define private ptr @"get_underlying_type_ref 43opaque_result_with_conditional_availabilityAA34test_multiple_single_multiplatformQryFQOQr"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %conditional-0
// CHECK: conditional-0: ; preds = %entry
// CHECK-NEXT: br label %cond-0-0
// CHECK: cond-0-0: ; preds = %conditional-0
// CHECK-NEXT: [[COND_1:%.*]] = call i32 @__isPlatformVersionAtLeast(i32 1, i32 100, i32 0, i32 1)
// CHECK-NEXT: [[IS_AT_LEAST:%.*]] = icmp ne i32 [[COND_1]], 0
// CHECK-NEXT: br i1 [[IS_AT_LEAST]], label %result-0, label %universal
// CHECK: result-0: ; preds = %cond-0-0
// CHECK-NEXT: ret ptr {{.*}} @"$s43opaque_result_with_conditional_availability1AVMf"
// CHECK: universal: ; preds = %cond-0-0
// CHECK-NEXT: ret ptr {{.*}} @"$s43opaque_result_with_conditional_availability1CVMf"
// CHECK-NEXT: }

// CHECK: define private ptr @"get_underlying_witness 43opaque_result_with_conditional_availabilityAA34test_multiple_single_multiplatformQryFQOxAA1PHC"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %conditional-0
// CHECK: conditional-0: ; preds = %entry
// CHECK-NEXT: br label %cond-0-0
// CHECK: cond-0-0: ; preds = %conditional-0
// CHECK-NEXT: [[COND_1:%.*]] = call i32 @__isPlatformVersionAtLeast(i32 1, i32 100, i32 0, i32 1)
// CHECK-NEXT: [[IS_AT_LEAST:%.*]] = icmp ne i32 [[COND_1]], 0
// CHECK-NEXT: br i1 [[IS_AT_LEAST]], label %result-0, label %universal
// CHECK: result-0: ; preds = %cond-0-0
// CHECK-NEXT: ret ptr @"$s43opaque_result_with_conditional_availability1AVAA1PAAWP"
// CHECK: universal: ; preds = %cond-0-0
// CHECK-NEXT: ret ptr @"$s43opaque_result_with_conditional_availability1CVAA1PAAWP"
// CHECK-NEXT: }

func test_multiple_conds() -> some P {
if #available(macOS 100.0.1, *) {
return A()
Expand Down Expand Up @@ -208,3 +251,51 @@ func test_cross_module() -> some SomeProtocol {
// CHECK-NEXT: [[R0:%.*]] = call ptr @"$s49opaque_result_with_conditional_availability_types12PublicStructVAcA12SomeProtocolAAWl"()
// CHECK-NEXT: ret ptr [[R0]]
// CHECK: }

func test_other_platform_available() -> some P {
if #available(iOS 100, *) {
return D() // Always executed on macOS
}

return C() // Never executed on macOS
}

// CHECK: define private ptr @"get_underlying_type_ref 43opaque_result_with_conditional_availabilityAA29test_other_platform_availableQryFQOQr"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %universal

// CHECK: universal:
// CHECK-NEXT: ret ptr getelementptr inbounds (<{ ptr, ptr, i64, ptr }>, ptr @"$s43opaque_result_with_conditional_availability1DVMf", i32 0, i32 2)
// CHECK-NEXT: }

// CHECK: define private ptr @"get_underlying_witness 43opaque_result_with_conditional_availabilityAA29test_other_platform_availableQryFQOxAA1PHC"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %universal

// CHECK: universal:
// CHECK-NEXT: ret ptr @"$s43opaque_result_with_conditional_availability1DVAA1PAAWP"
// CHECK-NEXT: }

func test_other_platform_unavailable() -> some P {
if #unavailable(iOS 100) {
return C() // Never executed on macOS
}

return D() // Always executed on macOS
}

// CHECK: define private ptr @"get_underlying_type_ref 43opaque_result_with_conditional_availabilityAA31test_other_platform_unavailableQryFQOQr"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %universal

// CHECK: universal:
// CHECK-NEXT: ret ptr getelementptr inbounds (<{ ptr, ptr, i64, ptr }>, ptr @"$s43opaque_result_with_conditional_availability1DVMf", i32 0, i32 2)
// CHECK-NEXT: }

// CHECK: define private ptr @"get_underlying_witness 43opaque_result_with_conditional_availabilityAA31test_other_platform_unavailableQryFQOxAA1PHC"(ptr %0)
// CHECK-NEXT: entry:
// CHECK-NEXT: br label %universal

// CHECK: universal:
// CHECK-NEXT: ret ptr @"$s43opaque_result_with_conditional_availability1DVAA1PAAWP"
// CHECK-NEXT: }
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,34 @@ public struct Example {
}
}

public func test() -> some P {
public func testAvailableQueryWithUniversalResult() -> some P {
if #available(macOS 100.0.1, *) {
return Tuple<(Int, Int)>((0, 0))
}

return Empty()
}

public func testUnavailable() -> some P {
public func testUnavailableQueryWithLimitedResult() -> some P {
if #unavailable(macOS 100.0.1) {
return Tuple<(Int, Int)>((0, 1))
}

return Empty()
}

public func test_return_from_conditional() -> some P {
public func testAvailableQueryWithLimitedResult() -> some P {
if #available(macOS 10.15, *) {
return Named()
}

return Tuple<(String, Int)>(("", 0))
}

// This used to crash during serialization because
// @available negates availability condition.
@available(macOS, unavailable)
public func testUnusable() -> some P {
public func testInactiveAvailableQuery() -> some P {
if #available(iOS 50, *) {
return Named()
return Empty()
}

return Tuple<(String, Int)>(("", 0))
return Named()
}
10 changes: 7 additions & 3 deletions test/Serialization/opaque_with_availability_cross_module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,22 @@ public struct Test {
}
}

let result = LimitedAvailOpaque.test()
let result = LimitedAvailOpaque.testAvailableQueryWithUniversalResult()
result.hello()
// CHECK: Hello from Empty

Test().sayHello()
// CHECK: Hello from Empty
// CHECK: Hello from Tuple

let conditionalR = LimitedAvailOpaque.test_return_from_conditional()
let conditionalR = LimitedAvailOpaque.testAvailableQueryWithLimitedResult()
conditionalR.hello()
// CHECK: Hello from Named

let unavailableTest = LimitedAvailOpaque.testUnavailable()
let unavailableTest = LimitedAvailOpaque.testUnavailableQueryWithLimitedResult()
unavailableTest.hello()
// CHECK: Hello from Tuple

let inactiveTest = LimitedAvailOpaque.testInactiveAvailableQuery()
inactiveTest.hello()
// CHECK: Hello from Empty