Skip to content

Commit

Permalink
Merge pull request swiftlang#34715 from Jumhyn/SR-13815
Browse files Browse the repository at this point in the history
[Sema] Always look through optionals for unresolved member lookup
  • Loading branch information
xedin authored Dec 5, 2020
2 parents 8104d5f + 6b0f5da commit 65db86b
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 4 deletions.
2 changes: 2 additions & 0 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,8 @@ enum ScoreKind {
SK_ForwardTrailingClosure,
/// A use of a disfavored overload.
SK_DisfavoredOverload,
/// A member for an \c UnresolvedMemberExpr found via unwrapped optional base.
SK_UnresolvedMemberViaOptional,
/// An implicit force of an implicitly unwrapped optional value.
SK_ForceUnchecked,
/// A user-defined conversion.
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/CSRanking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ static StringRef getScoreKindName(ScoreKind kind) {
case SK_DisfavoredOverload:
return "disfavored overload";

case SK_UnresolvedMemberViaOptional:
return "unwrapping optional at unresolved member base";

case SK_ForceUnchecked:
return "force of an implicitly unwrapped optional";

Expand Down
5 changes: 2 additions & 3 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6777,13 +6777,12 @@ performMemberLookup(ConstraintKind constraintKind, DeclNameRef memberName,
// through optional types.
//
// FIXME: Unify with the above code path.
if (result.ViableCandidates.empty() &&
baseObjTy->is<AnyMetatypeType>() &&
if (baseObjTy->is<AnyMetatypeType>() &&
constraintKind == ConstraintKind::UnresolvedValueMember) {
if (auto objectType = instanceTy->getOptionalObjectType()) {
// If we don't have a wrapped type yet, we can't look through the optional
// type.
if (objectType->getAs<TypeVariableType>()) {
if (objectType->getAs<TypeVariableType>() && result.ViableCandidates.empty()) {
MemberLookupResult result;
result.OverallResult = MemberLookupResult::Unsolved;
return result;
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2802,6 +2802,11 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
choice.getDecl()->getAttrs().hasAttribute<DisfavoredOverloadAttr>()) {
increaseScore(SK_DisfavoredOverload);
}

if (choice.getKind() == OverloadChoiceKind::DeclViaUnwrappedOptional &&
locator->isLastElement<LocatorPathElt::UnresolvedMember>()) {
increaseScore(SK_UnresolvedMemberViaOptional);
}
}

Type ConstraintSystem::simplifyTypeImpl(Type type,
Expand Down
47 changes: 47 additions & 0 deletions test/expr/delayed-ident/optional_overload.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %target-typecheck-verify-swift -dump-ast > %t.dump
// RUN: %FileCheck %s < %t.dump

// SR-13815
extension Optional {
func sr13815() -> SR13815? { SR13815() }
static func sr13815_2() -> SR13815? { SR13815() }
static func sr13815_3() -> SR13815? { SR13815() }
static var sr13815_wrongType: Int { 0 }
static var sr13815_overload: SR13815 { SR13815() }
init(overloaded: Void) { self = nil }
}

struct SR13815 {
static var sr13815: SR13815? = SR13815()
static var sr13815_2: SR13815? = SR13815()
static var sr13815_wrongType: SR13815? { SR13815() }
static var p_SR13815: SR13815? { SR13815() }
static func sr13815_3() -> SR13815? { SR13815() }
static var sr13815_overload: SR13815? { SR13815() }
init(overloaded: Void) {}
init?(failable: Void) {}
init() {}
}

protocol P_SR13815 {}
extension Optional: P_SR13815 where Wrapped: Equatable {
static func p_SR13815() {}
}

let _: SR13815? = .sr13815
let _: SR13815? = .sr13815_wrongType
let _: SR13815? = .init()
let _: SR13815? = .sr13815() // expected-error {{instance member 'sr13815' cannot be used on type 'SR13815?'}}
let _: SR13815? = .sr13815_2()
let _: SR13815? = .init(SR13815())
let _: SR13815? = .init(overloaded: ())
// If members exist on Optional and Wrapped, always choose the one on optional
// CHECK: declref_expr {{.*}} location={{.*}}optional_overload.swift:37
// CHECK-SAME: decl=optional_overload.(file).Optional extension.init(overloaded:)
let _: SR13815? = .sr13815_overload
// Should choose the overload from Optional even if the Wrapped overload would otherwise have a better score
// CHECK: member_ref_expr {{.*}} location={{.*}}optional_overload.swift:41
// CHECK-SAME: decl=optional_overload.(file).Optional extension.sr13815_overload
let _: SR13815? = .init(failable: ())
let _: SR13815? = .sr13815_3()
let _: SR13815? = .p_SR13815
3 changes: 2 additions & 1 deletion unittests/Sema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
add_swift_unittest(swiftSemaTests
SemaFixture.cpp
BindingInferenceTests.cpp
ConstraintSimplificationTests.cpp)
ConstraintSimplificationTests.cpp
UnresolvedMemberLookupTests.cpp)

target_link_libraries(swiftSemaTests
PRIVATE
Expand Down
33 changes: 33 additions & 0 deletions unittests/Sema/SemaFixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,39 @@ Type SemaTest::getStdlibType(StringRef name) const {
return Type();
}

NominalTypeDecl *SemaTest::getStdlibNominalTypeDecl(StringRef name) const {
auto typeName = Context.getIdentifier(name);

auto *stdlib = Context.getStdlibModule();

llvm::SmallVector<ValueDecl *, 4> results;
stdlib->lookupValue(typeName, NLKind::UnqualifiedLookup, results);

if (results.size() != 1)
return nullptr;

return dyn_cast<NominalTypeDecl>(results.front());
}

VarDecl *SemaTest::addExtensionVarMember(NominalTypeDecl *decl,
StringRef name, Type type) const {
auto *ext = ExtensionDecl::create(Context, SourceLoc(), nullptr, { }, DC,
nullptr);
decl->addExtension(ext);
ext->setExtendedNominal(decl);

auto *VD = new (Context) VarDecl(/*isStatic=*/ true, VarDecl::Introducer::Var,
/*nameLoc=*/ SourceLoc(),
Context.getIdentifier(name), ext);

ext->addMember(VD);
auto *pat = new (Context) NamedPattern(VD);
VD->setNamingPattern(pat);
pat->setType(type);

return VD;
}

ProtocolType *SemaTest::createProtocol(llvm::StringRef protocolName,
Type parent) {
auto *PD = new (Context)
Expand Down
5 changes: 5 additions & 0 deletions unittests/Sema/SemaFixture.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class SemaTest : public SemaTestBase {
protected:
Type getStdlibType(StringRef name) const;

NominalTypeDecl *getStdlibNominalTypeDecl(StringRef name) const;

VarDecl *addExtensionVarMember(NominalTypeDecl *decl, StringRef name,
Type type) const;

ProtocolType *createProtocol(llvm::StringRef protocolName,
Type parent = Type());

Expand Down
90 changes: 90 additions & 0 deletions unittests/Sema/UnresolvedMemberLookupTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===--- UnresolvedMemberLookupTests.cpp --------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "SemaFixture.h"
#include "swift/Sema/ConstraintSystem.h"

using namespace swift;
using namespace swift::unittest;
using namespace swift::constraints;

TEST_F(SemaTest, TestLookupAlwaysLooksThroughOptionalBase) {
auto *intTypeDecl = getStdlibNominalTypeDecl("Int");
auto *optTypeDecl = getStdlibNominalTypeDecl("Optional");
auto intType = intTypeDecl->getDeclaredType();
auto intOptType = OptionalType::get(intType);
auto stringType = getStdlibType("String");

auto *intMember = addExtensionVarMember(intTypeDecl, "test", intOptType);
addExtensionVarMember(optTypeDecl, "test", stringType);

auto *UME = new (Context)
UnresolvedMemberExpr(SourceLoc(), DeclNameLoc(),
DeclNameRef(Context.getIdentifier("test")), true);
auto *UMCRE = new (Context) UnresolvedMemberChainResultExpr(UME, UME);

ConstraintSystem cs(DC, ConstraintSystemOptions());
cs.generateConstraints(UMCRE, DC);
cs.addConstraint(
ConstraintKind::Conversion, cs.getType(UMCRE), intOptType,
cs.getConstraintLocator(UMCRE, ConstraintLocator::ContextualType));
SmallVector<Solution, 2> solutions;
cs.solve(solutions);

// We should have a solution.
ASSERT_EQ(solutions.size(), 1);

auto &solution = solutions[0];
auto *locator = cs.getConstraintLocator(UME,
ConstraintLocator::UnresolvedMember);
auto choice = solution.getOverloadChoice(locator).choice;

// The `test` member on `Int` should be selected.
ASSERT_EQ(choice.getDecl(), intMember);
}

TEST_F(SemaTest, TestLookupPrefersResultsOnOptionalRatherThanBase) {
auto *intTypeDecl = getStdlibNominalTypeDecl("Int");
auto *optTypeDecl = getStdlibNominalTypeDecl("Optional");
auto intType = intTypeDecl->getDeclaredType();
auto intOptType = OptionalType::get(intType);

addExtensionVarMember(intTypeDecl, "test", intOptType);
auto *optMember = addExtensionVarMember(optTypeDecl, "test", intType);

auto *UME = new (Context)
UnresolvedMemberExpr(SourceLoc(), DeclNameLoc(),
DeclNameRef(Context.getIdentifier("test")), true);
auto *UMCRE = new (Context) UnresolvedMemberChainResultExpr(UME, UME);

ConstraintSystem cs(DC, ConstraintSystemOptions());
cs.generateConstraints(UMCRE, DC);
cs.addConstraint(
ConstraintKind::Conversion, cs.getType(UMCRE), intOptType,
cs.getConstraintLocator(UMCRE, ConstraintLocator::ContextualType));
SmallVector<Solution, 2> solutions;
cs.solve(solutions);

// We should have a solution.
ASSERT_EQ(solutions.size(), 1);

auto &solution = solutions[0];
auto *locator = cs.getConstraintLocator(UME,
ConstraintLocator::UnresolvedMember);
auto choice = solution.getOverloadChoice(locator).choice;
auto score = solution.getFixedScore();

// The `test` member on `Optional` should be chosen over the member on `Int`,
// even though the score is otherwise worse.
ASSERT_EQ(score.Data[SK_ValueToOptional], 1);
ASSERT_EQ(choice.getDecl(), optMember);
}

0 comments on commit 65db86b

Please sign in to comment.