Skip to content

[4.0] Handle IUO unwraps in key paths. #11005

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 1 commit into from
Jul 18, 2017
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
6 changes: 3 additions & 3 deletions lib/SIL/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3825,20 +3825,20 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
break;
}
case KeyPathPatternComponent::Kind::OptionalChain: {
require(OptionalType::get(componentTy)->isEqual(baseTy),
require(baseTy->getAnyOptionalObjectType()->isEqual(componentTy),
"chaining component should unwrap optional");
require(leafTy->getAnyOptionalObjectType(),
"key path with chaining component should have optional "
"result");
break;
}
case KeyPathPatternComponent::Kind::OptionalForce: {
require(OptionalType::get(componentTy)->isEqual(baseTy),
require(baseTy->getAnyOptionalObjectType()->isEqual(componentTy),
"forcing component should unwrap optional");
break;
}
case KeyPathPatternComponent::Kind::OptionalWrap: {
require(OptionalType::get(baseTy)->isEqual(componentTy),
require(componentTy->getAnyOptionalObjectType()->isEqual(baseTy),
"wrapping component should wrap optional");
break;
}
Expand Down
26 changes: 26 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4100,6 +4100,19 @@ namespace {
}
}

// Unwrap if we needed to look through an IUO to find the
// property.
if (auto objTy = cs.lookThroughImplicitlyUnwrappedOptionalType(
baseTy->getRValueType())) {
if (baseTy->is<LValueType>())
baseTy = LValueType::get(objTy);
else
baseTy = objTy;

resolvedComponents.push_back(
KeyPathExpr::Component::forOptionalForce(baseTy, SourceLoc()));
}

auto dc = property->getInnermostDeclContext();
SmallVector<Substitution, 4> subs;
if (auto sig = dc->getGenericSignatureOfContext()) {
Expand Down Expand Up @@ -4131,6 +4144,19 @@ namespace {
diag::expr_keypath_mutating_getter,
subscript->getFullName());
}

// Unwrap if we needed to look through an IUO to find the
// subscript.
if (auto objTy = cs.lookThroughImplicitlyUnwrappedOptionalType(
baseTy->getRValueType())) {
if (baseTy->is<LValueType>())
baseTy = LValueType::get(objTy);
else
baseTy = objTy;

resolvedComponents.push_back(
KeyPathExpr::Component::forOptionalForce(baseTy, SourceLoc()));
}

auto dc = subscript->getInnermostDeclContext();
SmallVector<Substitution, 4> subs;
Expand Down
16 changes: 12 additions & 4 deletions stdlib/public/core/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,9 @@ internal struct RawKeyPathComponent {
return .continue(get(base, argument?.data.baseAddress ?? rawGet))

case .optionalChain:
_sanityCheck(CurValue.self == Optional<NewValue>.self,
// TODO: IUO shouldn't be a first class type
_sanityCheck(CurValue.self == Optional<NewValue>.self
|| CurValue.self == ImplicitlyUnwrappedOptional<NewValue>.self,
"should be unwrapping optional value")
_sanityCheck(_isOptional(LeafValue.self),
"leaf result should be optional")
Expand All @@ -1033,12 +1035,16 @@ internal struct RawKeyPathComponent {
}

case .optionalForce:
_sanityCheck(CurValue.self == Optional<NewValue>.self,
// TODO: IUO shouldn't be a first class type
_sanityCheck(CurValue.self == Optional<NewValue>.self
|| CurValue.self == ImplicitlyUnwrappedOptional<NewValue>.self,
"should be unwrapping optional value")
return .continue(unsafeBitCast(base, to: Optional<NewValue>.self)!)

case .optionalWrap:
_sanityCheck(NewValue.self == Optional<CurValue>.self,
// TODO: IUO shouldn't be a first class type
_sanityCheck(NewValue.self == Optional<CurValue>.self
|| CurValue.self == ImplicitlyUnwrappedOptional<CurValue>.self,
"should be wrapping optional value")
return .continue(
unsafeBitCast(base as Optional<CurValue>, to: NewValue.self))
Expand Down Expand Up @@ -1121,7 +1127,9 @@ internal struct RawKeyPathComponent {
return UnsafeRawPointer(Builtin.addressof(&writeback.value))

case .optionalForce:
_sanityCheck(CurValue.self == Optional<NewValue>.self,
// TODO: ImplicitlyUnwrappedOptional should not be a first-class type
_sanityCheck(CurValue.self == Optional<NewValue>.self
|| CurValue.self == ImplicitlyUnwrappedOptional<NewValue>.self,
"should be unwrapping an optional value")
// Optional's layout happens to always put the payload at the start
// address of the Optional value itself, if a value is present at all.
Expand Down
26 changes: 26 additions & 0 deletions test/SILGen/keypaths.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,29 @@ func keyPathForStorageQualified() {
// CHECK-SAME: settable_property $StorageQualified, id ##FinalStorageQualified.disowned
_ = \FinalStorageQualified.disowned
}

struct IUOProperty {
var iuo: IUOBlob!
}

struct IUOBlob {
var x: Int
subscript(y: String) -> String {
get { return y }
set {}
}
}

// CHECK-LABEL: sil hidden @{{.*}}iuoKeyPaths
func iuoKeyPaths() {
// CHECK: = keypath $WritableKeyPath<IUOProperty, Int>,
// CHECK-SAME: stored_property #IUOProperty.iuo
// CHECK-SAME: optional_force
// CHECK-SAME: stored_property #IUOBlob.x
_ = \IUOProperty.iuo.x
// CHECK: = keypath $WritableKeyPath<IUOProperty, Int>,
// CHECK-SAME: stored_property #IUOProperty.iuo
// CHECK-SAME: optional_force
// CHECK-SAME: stored_property #IUOBlob.x
_ = \IUOProperty.iuo!.x
}
23 changes: 23 additions & 0 deletions test/stdlib/KeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -523,4 +523,27 @@ keyPath.test("writebacks nest properly") {
expectEqual(nestedWritebackLog, 0x383736)
}

struct IUOWrapper {
var wrapped: IUOWrapped!
}

struct IUOWrapped {
var value: Int
}

keyPath.test("IUO and key paths") {
var subject = IUOWrapper(wrapped: IUOWrapped(value: 1989))
let kp1 = \IUOWrapper.wrapped.value

expectEqual(subject[keyPath: kp1], 1989)
subject[keyPath: kp1] = 1738
expectEqual(subject[keyPath: kp1], 1738)
expectEqual(subject.wrapped.value, 1738)

let kp2 = \IUOWrapper.wrapped!.value

expectEqual(kp1, kp2)
expectEqual(kp1.hashValue, kp2.hashValue)
}

runAllTests()