diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 4a5c4800bbd..4c2295fad64 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -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. + 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()) { baseIsInstance = false; @@ -677,6 +696,12 @@ namespace { auto &tc = cs.getTypeChecker(); auto baseTy = base->getType()->getRValueType(); + // Handle accesses that implicitly look through UncheckedOptional. + 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. + 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(); + auto toGenericType = toType->castTo(); + 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(); - auto toGenericType = toType->castTo(); - 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); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 18e10a01fa9..b8a3cc34af8 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1328,6 +1328,11 @@ ConstraintSystem::simplifyMemberConstraint(const Constraint &constraint) { Type baseTy = simplifyType(constraint.getFirstType()); Type baseObjTy = baseTy->getRValueType(); + // Try to look through UncheckedOptional; 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; diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index efcef8db210..c871e0cec20 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -1055,6 +1055,45 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator, } } +/// Given that we're accessing a member of an UncheckedOptional, 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, 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. + } else { + assert(DC->isModuleScopeContext()); + return (DC == D->getModuleScopeContext()); + } + } +} + +Type ConstraintSystem::lookThroughUncheckedOptionalType(Type type) { + if (auto boundTy = type->getAs()) { + 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 &substituting) { return type.transform([&](Type type) -> Type { diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h index 58a705e56c3..8bdb997539c 100644 --- a/lib/Sema/ConstraintSystem.h +++ b/lib/Sema/ConstraintSystem.h @@ -1510,6 +1510,11 @@ public: void addOverloadSet(Type boundType, ArrayRef choices, ConstraintLocator *locator); + /// If the given type is UncheckedOptional, 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; } diff --git a/test/Constraints/unchecked_optional.swift b/test/Constraints/unchecked_optional.swift new file mode 100644 index 00000000000..f6f355fdbab --- /dev/null +++ b/test/Constraints/unchecked_optional.swift @@ -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] +}