mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Implicitly look through UncheckedOptional<T> when it's the
base of a member access or subscript. Swift SVN r12345
This commit is contained in:
@@ -305,6 +305,17 @@ namespace {
|
||||
Expr *coerceViaUserConversion(Expr *expr, Type toType,
|
||||
ConstraintLocatorBuilder locator);
|
||||
|
||||
/// \brief Coerce an expression of (possibly unchecked) optional
|
||||
/// type to have a different (possibly unchecked) optional type.
|
||||
Expr *coerceOptionalToOptional(Expr *expr, Type toType,
|
||||
ConstraintLocatorBuilder locator);
|
||||
|
||||
/// \brief Coerce an expression of unchecked optional type to its
|
||||
/// underlying value type, in the correct way for an implicit
|
||||
/// look-through.
|
||||
Expr *coerceUncheckedOptionalToValue(Expr *expr, Type objTy,
|
||||
ConstraintLocatorBuilder locator);
|
||||
|
||||
public:
|
||||
/// \brief Build a reference to the given declaration.
|
||||
Expr *buildDeclRef(ValueDecl *decl, SourceLoc loc, Type openedType,
|
||||
@@ -355,9 +366,17 @@ namespace {
|
||||
auto &tc = cs.getTypeChecker();
|
||||
auto &context = tc.Context;
|
||||
|
||||
Type baseTy = base->getType()->getRValueType();
|
||||
|
||||
// Handle accesses that implicitly look through UncheckedOptional<T>.
|
||||
if (auto objTy = cs.lookThroughUncheckedOptionalType(baseTy)) {
|
||||
base = coerceUncheckedOptionalToValue(base, objTy, locator);
|
||||
if (!base) return nullptr;
|
||||
baseTy = objTy;
|
||||
}
|
||||
|
||||
// Figure out the actual base type, and whether we have an instance of
|
||||
// that type or its metatype.
|
||||
Type baseTy = base->getType()->getRValueType();
|
||||
bool baseIsInstance = true;
|
||||
if (auto baseMeta = baseTy->getAs<MetatypeType>()) {
|
||||
baseIsInstance = false;
|
||||
@@ -677,6 +696,12 @@ namespace {
|
||||
auto &tc = cs.getTypeChecker();
|
||||
auto baseTy = base->getType()->getRValueType();
|
||||
|
||||
// Handle accesses that implicitly look through UncheckedOptional<T>.
|
||||
if (auto objTy = cs.lookThroughUncheckedOptionalType(baseTy)) {
|
||||
base = coerceUncheckedOptionalToValue(base, objTy, locator);
|
||||
if (!base) return nullptr;
|
||||
}
|
||||
|
||||
// Figure out the index and result types.
|
||||
auto containerTy
|
||||
= subscript->getDeclContext()->getDeclaredTypeOfContext();
|
||||
@@ -1490,13 +1515,22 @@ namespace {
|
||||
selected.openedType,
|
||||
cs.getConstraintLocator(expr, { }));
|
||||
|
||||
case OverloadChoiceKind::TupleIndex:
|
||||
case OverloadChoiceKind::TupleIndex: {
|
||||
auto base = expr->getBase();
|
||||
auto baseTy = base->getType()->getRValueType();
|
||||
if (auto objTy = cs.lookThroughUncheckedOptionalType(baseTy)) {
|
||||
base = coerceUncheckedOptionalToValue(base, objTy,
|
||||
cs.getConstraintLocator(base, { }));
|
||||
if (!base) return nullptr;
|
||||
}
|
||||
|
||||
return new (cs.getASTContext()) TupleElementExpr(
|
||||
expr->getBase(),
|
||||
base,
|
||||
expr->getDotLoc(),
|
||||
selected.choice.getTupleIndex(),
|
||||
expr->getNameLoc(),
|
||||
simplifyType(expr->getType()));
|
||||
}
|
||||
|
||||
case OverloadChoiceKind::BaseType: {
|
||||
// FIXME: Losing ".0" sugar here.
|
||||
@@ -1617,6 +1651,16 @@ namespace {
|
||||
}
|
||||
|
||||
Expr *visitTupleElementExpr(TupleElementExpr *expr) {
|
||||
// Handle accesses that implicitly look through UncheckedOptional<T>.
|
||||
auto base = expr->getBase();
|
||||
auto baseTy = base->getType()->getRValueType();
|
||||
if (auto objTy = cs.lookThroughUncheckedOptionalType(baseTy)) {
|
||||
base = coerceUncheckedOptionalToValue(base, objTy,
|
||||
cs.getConstraintLocator(base, { }));
|
||||
if (!base) return nullptr;
|
||||
expr->setBase(base);
|
||||
}
|
||||
|
||||
simplifyExprType(expr);
|
||||
return expr;
|
||||
}
|
||||
@@ -2653,6 +2697,47 @@ Expr *ExprRewriter::coerceViaUserConversion(Expr *expr, Type toType,
|
||||
return coerceToType(expr, toType, locator);
|
||||
}
|
||||
|
||||
Expr *ExprRewriter::coerceOptionalToOptional(Expr *expr, Type toType,
|
||||
ConstraintLocatorBuilder locator) {
|
||||
auto &tc = cs.getTypeChecker();
|
||||
Type fromType = expr->getType();
|
||||
|
||||
auto fromGenericType = fromType->castTo<BoundGenericType>();
|
||||
auto toGenericType = toType->castTo<BoundGenericType>();
|
||||
assert(fromGenericType->getDecl()->classifyAsOptionalType());
|
||||
assert(toGenericType->getDecl()->classifyAsOptionalType());
|
||||
tc.requireOptionalIntrinsics(expr->getLoc());
|
||||
|
||||
Type fromValueType = fromGenericType->getGenericArgs()[0];
|
||||
Type toValueType = toGenericType->getGenericArgs()[0];
|
||||
|
||||
expr = new (tc.Context) BindOptionalExpr(expr, expr->getSourceRange().End,
|
||||
fromValueType);
|
||||
expr->setImplicit(true);
|
||||
expr = coerceToType(expr, toValueType, locator);
|
||||
if (!expr) return nullptr;
|
||||
|
||||
expr = new (tc.Context) InjectIntoOptionalExpr(expr, toType);
|
||||
|
||||
expr = new (tc.Context) OptionalEvaluationExpr(expr, toType);
|
||||
expr->setImplicit(true);
|
||||
return expr;
|
||||
}
|
||||
|
||||
Expr *ExprRewriter::coerceUncheckedOptionalToValue(Expr *expr, Type objTy,
|
||||
ConstraintLocatorBuilder locator) {
|
||||
// Coerce to an r-value.
|
||||
auto rvalueTy = expr->getType()->getRValueType();
|
||||
assert(rvalueTy->getUncheckedOptionalObjectType()->isEqual(objTy));
|
||||
|
||||
expr = coerceToType(expr, rvalueTy, /*bogus?*/ locator);
|
||||
if (!expr) return nullptr;
|
||||
|
||||
expr = new (cs.getTypeChecker().Context) ForceValueExpr(expr, expr->getEndLoc());
|
||||
expr->setType(objTy);
|
||||
expr->setImplicit();
|
||||
return expr;
|
||||
}
|
||||
|
||||
Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
|
||||
ConstraintLocatorBuilder locator) {
|
||||
@@ -2763,28 +2848,8 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
|
||||
}
|
||||
|
||||
case ConversionRestrictionKind::UncheckedOptionalToOptional:
|
||||
case ConversionRestrictionKind::OptionalToOptional: {
|
||||
auto fromGenericType = fromType->castTo<BoundGenericType>();
|
||||
auto toGenericType = toType->castTo<BoundGenericType>();
|
||||
assert(fromGenericType->getDecl()->classifyAsOptionalType());
|
||||
assert(toGenericType->getDecl()->classifyAsOptionalType());
|
||||
tc.requireOptionalIntrinsics(expr->getLoc());
|
||||
|
||||
Type fromValueType = fromGenericType->getGenericArgs()[0];
|
||||
Type toValueType = toGenericType->getGenericArgs()[0];
|
||||
|
||||
expr = new (tc.Context) BindOptionalExpr(expr, expr->getSourceRange().End,
|
||||
fromValueType);
|
||||
expr->setImplicit(true);
|
||||
expr = coerceToType(expr, toValueType, locator);
|
||||
if (!expr) return nullptr;
|
||||
|
||||
expr = new (tc.Context) InjectIntoOptionalExpr(expr, toType);
|
||||
|
||||
expr = new (tc.Context) OptionalEvaluationExpr(expr, toType);
|
||||
expr->setImplicit(true);
|
||||
return expr;
|
||||
}
|
||||
case ConversionRestrictionKind::OptionalToOptional:
|
||||
return coerceOptionalToOptional(expr, toType, locator);
|
||||
|
||||
case ConversionRestrictionKind::User:
|
||||
return coerceViaUserConversion(expr, toType, locator);
|
||||
|
||||
@@ -1328,6 +1328,11 @@ ConstraintSystem::simplifyMemberConstraint(const Constraint &constraint) {
|
||||
Type baseTy = simplifyType(constraint.getFirstType());
|
||||
Type baseObjTy = baseTy->getRValueType();
|
||||
|
||||
// Try to look through UncheckedOptional<T>; the result is always an r-value.
|
||||
if (auto objTy = lookThroughUncheckedOptionalType(baseObjTy)) {
|
||||
baseTy = baseObjTy = objTy;
|
||||
}
|
||||
|
||||
// Dig out the instance type.
|
||||
bool isMetatype = false;
|
||||
Type instanceTy = baseObjTy;
|
||||
|
||||
@@ -1055,6 +1055,45 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given that we're accessing a member of an UncheckedOptional<T>, is
|
||||
/// the DC one of the special cases where we should not instead look at T?
|
||||
static bool isPrivilegedAccessToUncheckedOptional(DeclContext *DC,
|
||||
NominalTypeDecl *D) {
|
||||
assert(D == DC->getASTContext().getUncheckedOptionalDecl());
|
||||
|
||||
// Walk up through the chain of current contexts.
|
||||
for (; ; DC = DC->getParent()) {
|
||||
assert(DC && "ran out of contexts before finding a module scope?");
|
||||
|
||||
// Look through local contexts.
|
||||
if (DC->isLocalContext()) {
|
||||
continue;
|
||||
|
||||
// If we're in a type context that's defining or extending
|
||||
// UncheckedOptional<T>, we're privileged.
|
||||
} else if (DC->isTypeContext()) {
|
||||
if (DC->getDeclaredTypeInContext()->getAnyNominal() == D)
|
||||
return true;
|
||||
|
||||
// Otherwise, we're privileged if we're within the same file that
|
||||
// defines UncheckedOptional<T>.
|
||||
} else {
|
||||
assert(DC->isModuleScopeContext());
|
||||
return (DC == D->getModuleScopeContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type ConstraintSystem::lookThroughUncheckedOptionalType(Type type) {
|
||||
if (auto boundTy = type->getAs<BoundGenericStructType>()) {
|
||||
auto boundDecl = boundTy->getDecl();
|
||||
if (boundDecl == TC.Context.getUncheckedOptionalDecl() &&
|
||||
!isPrivilegedAccessToUncheckedOptional(DC, boundDecl))
|
||||
return boundTy->getGenericArgs()[0];
|
||||
}
|
||||
return Type();
|
||||
}
|
||||
|
||||
Type ConstraintSystem::simplifyType(Type type,
|
||||
llvm::SmallPtrSet<TypeVariableType *, 16> &substituting) {
|
||||
return type.transform([&](Type type) -> Type {
|
||||
|
||||
@@ -1510,6 +1510,11 @@ public:
|
||||
void addOverloadSet(Type boundType, ArrayRef<OverloadChoice> choices,
|
||||
ConstraintLocator *locator);
|
||||
|
||||
/// If the given type is UncheckedOptional<T>, and we're in a context
|
||||
/// that should transparently look through UncheckedOptional types,
|
||||
/// return T.
|
||||
Type lookThroughUncheckedOptionalType(Type type);
|
||||
|
||||
/// \brief Retrieve the allocator used by this constraint system.
|
||||
llvm::BumpPtrAllocator &getAllocator() { return Allocator; }
|
||||
|
||||
|
||||
51
test/Constraints/unchecked_optional.swift
Normal file
51
test/Constraints/unchecked_optional.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
// RUN: %swift -parse -verify %s
|
||||
|
||||
class A {
|
||||
func do_a() {}
|
||||
|
||||
func do_b(x: Int) {}
|
||||
func do_b(x: Float) {}
|
||||
|
||||
func do_c(x: Int) {} // expected-note {{found this candidate}}
|
||||
func do_c(y: Int) {} // expected-note {{found this candidate}}
|
||||
}
|
||||
|
||||
func test0(a : @unchecked A?) {
|
||||
a.do_a()
|
||||
|
||||
a.do_b(1)
|
||||
a.do_b(5.0)
|
||||
|
||||
a.do_c(1) // expected-error {{ambiguous use of 'do_c'}}
|
||||
a.do_c(x: 1)
|
||||
}
|
||||
|
||||
func test1(a : @unchecked A?) {
|
||||
a?.do_a()
|
||||
|
||||
a?.do_b(1)
|
||||
a?.do_b(5.0)
|
||||
|
||||
// FIXME: this should really get diagnosed like the above
|
||||
a?.do_c(1) // expected-error {{expression does not type-check}}
|
||||
a?.do_c(x: 1)
|
||||
}
|
||||
|
||||
struct B {
|
||||
var x : Int
|
||||
}
|
||||
|
||||
func test2(var b : @unchecked B?) {
|
||||
var x = b.x
|
||||
b.x = x // expected-error {{cannot assign to the result of this expression}}
|
||||
}
|
||||
|
||||
struct Subscriptable {
|
||||
subscript(x : Int) -> Int {
|
||||
get: return x
|
||||
}
|
||||
}
|
||||
|
||||
func test3(x: @unchecked Subscriptable?) -> Int {
|
||||
return x[0]
|
||||
}
|
||||
Reference in New Issue
Block a user