Implicitly look through UncheckedOptional<T> when it's the

base of a member access or subscript.

Swift SVN r12345
This commit is contained in:
John McCall
2014-01-15 21:00:59 +00:00
parent 1ccf23487e
commit 7be7c20a27
5 changed files with 190 additions and 25 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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; }

View 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]
}