diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 06723d7a3b931..aa2f314404de1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -376,6 +376,10 @@ ERROR(cannot_convert_argument_value,none, "cannot convert value of type %0 to expected argument type %1", (Type,Type)) +ERROR(cannot_convert_unresolved_key_path_argument_value,none, + "cannot convert value of key path type to expected argument type %0", + (Type)) + ERROR(cannot_convert_argument_value_for_swift_func,none, "cannot convert value of type %0 to expected argument type %1 " "because %kind2 was not imported from C header", diff --git a/include/swift/Sema/CSBindings.h b/include/swift/Sema/CSBindings.h index f27f3065d922b..4ae14b9d376c1 100644 --- a/include/swift/Sema/CSBindings.h +++ b/include/swift/Sema/CSBindings.h @@ -71,6 +71,8 @@ enum class LiteralBindingKind : uint8_t { /// along with information that can be used to construct related /// bindings, e.g., the supertypes of a given type. struct PotentialBinding { + friend class BindingSet; + /// The type to which the type variable can be bound. Type BindingType; diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 75c8cecebcb68..b6ddbf3eb8db5 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -491,6 +491,10 @@ class TypeVariableType::Implementation { /// a type of a key path expression. bool isKeyPathType() const; + /// Determine whether this type variable represents a root type of a key path + /// expression. + bool isKeyPathRoot() const; + /// Determine whether this type variable represents a value type of a key path /// expression. bool isKeyPathValue() const; @@ -4008,20 +4012,6 @@ class ConstraintSystem { bool resolveClosure(TypeVariableType *typeVar, Type contextualType, ConstraintLocatorBuilder locator); - /// Given the fact a contextual type is now available for the type - /// variable representing one of the key path expressions, let's set a - /// pre-determined key path expression type. - /// - /// \param typeVar The type variable representing a key path expression. - /// \param contextualType The contextual type this key path would be - /// converted to. - /// \param locator The locator associated with contextual type. - /// - /// \returns `true` if it was possible to generate constraints for - /// the keyPath expression, `false` otherwise. - bool resolveKeyPath(TypeVariableType *typeVar, Type contextualType, - ConstraintLocatorBuilder locator); - /// Given the fact that contextual type is now available for the type /// variable representing a pack expansion type, let's resolve the expansion. /// diff --git a/lib/Sema/CSBindings.cpp b/lib/Sema/CSBindings.cpp index e5abaf6a1eff2..f223e8951a872 100644 --- a/lib/Sema/CSBindings.cpp +++ b/lib/Sema/CSBindings.cpp @@ -96,7 +96,7 @@ bool BindingSet::isDelayed() const { // Delay key path literal type binding until there is at least // one contextual binding (or default is promoted into a binding). - if (TypeVar->getImpl().isKeyPathType() && Bindings.empty()) + if (TypeVar->getImpl().isKeyPathType() && !Defaults.empty()) return true; if (isHole()) { @@ -178,7 +178,7 @@ bool BindingSet::isPotentiallyIncomplete() const { // contextual type or key path is resolved enough to infer // capability and promote default into a binding. if (TypeVar->getImpl().isKeyPathType()) - return Bindings.empty(); + return !Defaults.empty(); // If current type variable is associated with a code completion token // it's possible that it doesn't have enough contextual information @@ -430,6 +430,41 @@ void BindingSet::inferTransitiveBindings( &inferredBindings) { using BindingKind = AllowedBindingKind; + // If the current type variable represents a key path root type + // let's try to transitively infer its type through bindings of + // a key path type. + if (TypeVar->getImpl().isKeyPathRoot()) { + auto *locator = TypeVar->getImpl().getLocator(); + if (auto *keyPathTy = + CS.getType(locator->getAnchor())->getAs()) { + auto keyPathBindings = inferredBindings.find(keyPathTy); + if (keyPathBindings != inferredBindings.end()) { + auto &bindings = keyPathBindings->getSecond(); + + for (auto &binding : bindings.Bindings) { + auto bindingTy = binding.BindingType->lookThroughAllOptionalTypes(); + + Type inferredRootTy; + if (isKnownKeyPathType(bindingTy)) { + // AnyKeyPath doesn't have a root type. + if (bindingTy->isAnyKeyPath()) + continue; + + auto *BGT = bindingTy->castTo(); + inferredRootTy = BGT->getGenericArgs()[0]; + } else if (auto *fnType = bindingTy->getAs()) { + if (fnType->getNumParams() == 1) + inferredRootTy = fnType->getParams()[0].getParameterType(); + } + + if (inferredRootTy && !inferredRootTy->isTypeVariableOrMember()) + addBinding( + binding.withSameSource(inferredRootTy, BindingKind::Exact)); + } + } + } + } + for (const auto &entry : Info.SupertypeOf) { auto relatedBindings = inferredBindings.find(entry.first); if (relatedBindings == inferredBindings.end()) @@ -496,6 +531,24 @@ void BindingSet::inferTransitiveBindings( } } +static BoundGenericType *getKeyPathType(ASTContext &ctx, + KeyPathCapability capability, + Type rootType, Type valueType) { + switch (capability) { + case KeyPathCapability::ReadOnly: + return BoundGenericType::get(ctx.getKeyPathDecl(), /*parent=*/Type(), + {rootType, valueType}); + + case KeyPathCapability::Writable: + return BoundGenericType::get(ctx.getWritableKeyPathDecl(), + /*parent=*/Type(), {rootType, valueType}); + + case KeyPathCapability::ReferenceWritable: + return BoundGenericType::get(ctx.getReferenceWritableKeyPathDecl(), + /*parent=*/Type(), {rootType, valueType}); + } +} + void BindingSet::finalize( llvm::SmallDenseMap &inferredBindings) { inferTransitiveBindings(inferredBindings); @@ -538,6 +591,84 @@ void BindingSet::finalize( } } + if (TypeVar->getImpl().isKeyPathType()) { + auto &ctx = CS.getASTContext(); + + auto *keyPathLoc = TypeVar->getImpl().getLocator(); + auto *keyPath = castToExpr(keyPathLoc->getAnchor()); + + bool isValid; + llvm::Optional capability; + + std::tie(isValid, capability) = CS.inferKeyPathLiteralCapability(TypeVar); + + // Key path literal is not yet sufficiently resolved. + if (isValid && !capability) + return; + + // If the key path is sufficiently resolved we can add inferred binding + // to the set. + SmallSetVector updatedBindings; + for (const auto &binding : Bindings) { + auto bindingTy = binding.BindingType->lookThroughAllOptionalTypes(); + + assert(isKnownKeyPathType(bindingTy) || bindingTy->is()); + + // Functions don't have capability so we can simply add them. + if (bindingTy->is()) + updatedBindings.insert(binding); + } + + // Note that even though key path literal maybe be invalid it's + // still the best course of action to use contextual function type + // bindings because they allow to propagate type information from + // the key path into the context, so key path bindings are addded + // only if there is absolutely no other choice. + if (updatedBindings.empty()) { + auto rootTy = CS.getKeyPathRootType(keyPath); + + // A valid key path literal. + if (capability) { + // Note that the binding is formed using root & value + // type variables produced during constraint generation + // because at this point root is already known (otherwise + // inference wouldn't been able to determine key path's + // capability) and we always want to infer value from + // the key path and match it to a contextual type to produce + // better diagnostics. + auto keyPathTy = getKeyPathType(ctx, *capability, rootTy, + CS.getKeyPathValueType(keyPath)); + updatedBindings.insert( + {keyPathTy, AllowedBindingKind::Exact, keyPathLoc}); + } else if (CS.shouldAttemptFixes()) { + // If key path is structurally correct and has a resolved root + // type, let's promote the fallback type into a binding because + // root would have been inferred from explicit type already and + // it's benefitial for diagnostics to assign a non-placeholder + // type to key path literal to propagate root/value to the context. + if (!keyPath->hasSingleInvalidComponent() && + (keyPath->getParsedRoot() || + !CS.getFixedType(rootTy)->isTypeVariableOrMember())) { + auto fallback = llvm::find_if(Defaults, [](const auto &entry) { + return entry.second->getKind() == ConstraintKind::FallbackType; + }); + assert(fallback != Defaults.end()); + updatedBindings.insert( + {fallback->first, AllowedBindingKind::Exact, fallback->second}); + } else { + updatedBindings.insert(PotentialBinding::forHole( + TypeVar, CS.getConstraintLocator( + keyPath, ConstraintLocator::FallbackType))); + } + } + } + + Bindings = std::move(updatedBindings); + Defaults.clear(); + + return; + } + if (CS.shouldAttemptFixes() && locator->isLastElement()) { // Let's see whether this chain is valid, if it isn't then to avoid @@ -795,6 +926,12 @@ llvm::Optional ConstraintSystem::determineBestBindings( auto isViableForRanking = [this](const BindingSet &bindings) -> bool { auto *typeVar = bindings.getTypeVariable(); + // Key path root type variable is always viable because it can be + // transitively inferred from key path type during binding set + // finalization. + if (typeVar->getImpl().isKeyPathRoot()) + return true; + // Type variable representing a base of unresolved member chain should // always be considered viable for ranking since it's allow to infer // types from transitive protocol requirements. @@ -886,60 +1023,6 @@ void PotentialBindings::addDefault(Constraint *constraint) { void BindingSet::addDefault(Constraint *constraint) { auto defaultTy = constraint->getSecondType(); - - if (TypeVar->getImpl().isKeyPathType() && Bindings.empty()) { - if (constraint->getKind() == ConstraintKind::FallbackType) { - auto &ctx = CS.getASTContext(); - - bool isValid; - llvm::Optional capability; - - std::tie(isValid, capability) = CS.inferKeyPathLiteralCapability(TypeVar); - - if (!isValid) { - // If one of the references in a key path is invalid let's add - // a placeholder binding in diagnostic mode to indicate that - // the key path cannot be properly resolved. - if (CS.shouldAttemptFixes()) { - addBinding({PlaceholderType::get(ctx, TypeVar), - AllowedBindingKind::Exact, constraint}); - } - - // During normal solving the set has to stay empty. - return; - } - - if (capability) { - auto *keyPathType = defaultTy->castTo(); - - auto root = keyPathType->getGenericArgs()[0]; - auto value = keyPathType->getGenericArgs()[1]; - - switch (*capability) { - case KeyPathCapability::ReadOnly: - break; - - case KeyPathCapability::Writable: - keyPathType = BoundGenericType::get(ctx.getWritableKeyPathDecl(), - /*parent=*/Type(), {root, value}); - break; - - case KeyPathCapability::ReferenceWritable: - keyPathType = - BoundGenericType::get(ctx.getReferenceWritableKeyPathDecl(), - /*parent=*/Type(), {root, value}); - break; - } - - addBinding({keyPathType, AllowedBindingKind::Exact, constraint}); - } - - // If key path is not yet sufficiently resolved, don't add any - // bindings. - return; - } - } - Defaults.insert({defaultTy->getCanonicalType(), constraint}); } diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index fec8ad73daf1e..8341958cbb0af 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -2604,6 +2604,11 @@ bool ContextualFailure::diagnoseAsError() { if (diagnoseYieldByReferenceMismatch()) return true; + if (isExpr(anchor)) { + diagnostic = diag::expr_keypath_type_covert_to_contextual_type; + break; + } + if (isExpr(anchor) || isExpr(anchor)) { auto objectType = fromType->getOptionalObjectType(); @@ -7290,6 +7295,18 @@ bool ArgumentMismatchFailure::diagnoseAsError() { auto argType = getFromType(); + // Unresolved key path argument requires a tailored diagnostic + // that doesn't mention a fallback type - `KeyPath<_, _>`. + if (argType->isKeyPath() && !isKnownKeyPathType(paramType)) { + auto keyPathTy = argType->castTo(); + auto rootTy = keyPathTy->getGenericArgs()[0]; + if (rootTy->is()) { + emitDiagnostic(diag::cannot_convert_unresolved_key_path_argument_value, + paramType); + return true; + } + } + if (paramType->isAnyObject()) { emitDiagnostic(diag::cannot_convert_argument_value_anyobject, argType, paramType); diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 8fbdd567d6968..19ecfc5f81d90 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -3853,7 +3853,10 @@ namespace { // constraint system so we can find it later. CS.setType(E, i, base); } - + + auto valueLocator = + CS.getConstraintLocator(E, ConstraintLocator::KeyPathValue); + // If there was an optional chaining component, the end result must be // optional. if (didOptionalChain) { @@ -3862,8 +3865,7 @@ namespace { componentTypeVars.push_back(objTy); auto optTy = OptionalType::get(objTy); - CS.addConstraint(ConstraintKind::Conversion, base, optTy, - locator); + CS.addConstraint(ConstraintKind::Conversion, base, optTy, valueLocator); base = optTy; } @@ -3873,8 +3875,7 @@ namespace { (void)CS.recordFix(AllowKeyPathWithoutComponents::create(CS, locator)); } - auto valueLocator = - CS.getConstraintLocator(E, ConstraintLocator::KeyPathValue); + auto *value = CS.createTypeVariable(valueLocator, TVO_CanBindToNoEscape | TVO_CanBindToHole); CS.addConstraint(ConstraintKind::Equal, base, value, valueLocator); diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 423754a5245db..f982a7acb917d 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -3789,8 +3789,15 @@ ConstraintSystem::matchDeepEqualityTypes(Type type1, Type type2, getConstraintLocator(locator, {LocatorPathElt::GenericType(bound1), LocatorPathElt::GenericType(bound2)}); - auto argMatchingFlags = - subflags | TMF_ApplyingFix | TMF_MatchingGenericArguments; + auto argMatchingFlags = subflags; + // Allow the solver to produce separate fixes while matching + // key path's root/value to a contextual type instead of the + // standard one fix for all mismatched generic arguments + // because at least one side of such a relation would be resolved. + if (!isExpr(locator.trySimplifyToExpr())) { + argMatchingFlags |= TMF_ApplyingFix; + argMatchingFlags |= TMF_MatchingGenericArguments; + } // Optionals have a lot of special diagnostics and only one // generic argument so if we' re dealing with one, don't produce generic @@ -4471,13 +4478,6 @@ ConstraintSystem::matchTypesBindTypeVar( : getTypeMatchFailure(locator); } - if (typeVar->getImpl().isKeyPathType()) { - if (flags.contains(TMF_BindingTypeVariable)) - return resolveKeyPath(typeVar, type, locator) - ? getTypeMatchSuccess() - : getTypeMatchFailure(locator); - } - assignFixedType(typeVar, type, /*updateState=*/true, /*notifyInference=*/!flags.contains(TMF_BindingTypeVariable)); @@ -6675,6 +6675,10 @@ bool ConstraintSystem::repairFailures( if (lhs->isTypeVariableOrMember() || rhs->isTypeVariableOrMember()) break; + if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality) || + hasConversionOrRestriction(ConversionRestrictionKind::ValueToOptional)) + return false; + auto kpExpr = castToExpr(anchor); auto i = kpExpr->getComponents().size() - 1; auto lastCompLoc = @@ -6889,17 +6893,6 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, case ConstraintKind::ArgumentConversion: case ConstraintKind::OperatorArgumentConversion: { if (typeVar1) { - if (auto *locator = typeVar1->getImpl().getLocator()) { - // TODO(diagnostics): Only binding here for function types, because - // doing so for KeyPath types leaves the constraint system in an - // unexpected state for key path diagnostics should we fail. - if (locator->isLastElement() && - type2->is()) - return matchTypesBindTypeVar(typeVar1, type2, kind, - flags | TMF_BindingTypeVariable, - locator, formUnsolvedResult); - } - // Performance optimization: Propagate fully or partially resolved // contextual type down into the body of result builder transformed // closure by eagerly binding intermediate body result type to the @@ -11725,63 +11718,6 @@ bool ConstraintSystem::resolveClosure(TypeVariableType *typeVar, return !generateConstraints(AnyFunctionRef{closure}, closure->getBody()); } -bool ConstraintSystem::resolveKeyPath(TypeVariableType *typeVar, - Type contextualType, - ConstraintLocatorBuilder locator) { - auto *keyPathLocator = typeVar->getImpl().getLocator(); - auto *keyPath = castToExpr(keyPathLocator->getAnchor()); - - if (keyPath->hasSingleInvalidComponent()) { - assignFixedType(typeVar, contextualType); - return true; - } - - auto objectTy = contextualType->lookThroughAllOptionalTypes(); - { - auto &ctx = getASTContext(); - // `AnyKeyPath` and `PartialKeyPath` represent type-erased versions of - // `KeyPath`. - // - // In situations where `AnyKeyPath` or `PartialKeyPath` cannot be used - // directly i.e. passing an argument to a parameter represented by a - // `AnyKeyPath` or `PartialKeyPath`, let's attempt a `KeyPath` binding which - // would then be converted to a `AnyKeyPath` or `PartialKeyPath` since there - // is a subtype relationship between them. - if (objectTy->isAnyKeyPath()) { - auto root = getKeyPathRootType(keyPath); - auto value = getKeyPathValueType(keyPath); - - contextualType = - BoundGenericType::get(ctx.getKeyPathDecl(), Type(), {root, value}); - } else if (objectTy->isPartialKeyPath()) { - auto rootTy = objectTy->castTo()->getGenericArgs()[0]; - // Since partial key path is an erased version of `KeyPath`, the value - // type would never be used, which means that binding can use - // type variable generated for a result of key path expression. - auto valueTy = getKeyPathValueType(keyPath); - - contextualType = BoundGenericType::get(ctx.getKeyPathDecl(), Type(), - {rootTy, valueTy}); - } else if (isKnownKeyPathType(objectTy)) { - auto *keyPathTy = objectTy->castTo(); - auto args = keyPathTy->getGenericArgs(); - assert(args.size() == 2); - - auto root = args.front(); - auto value = getKeyPathValueType(keyPath); - // Make sure that key path always gets a chance to infer its - // value type from the member chain. - if (!value->isEqual(args.back())) { - contextualType = BoundGenericType::get( - keyPathTy->getDecl(), keyPathTy->getParent(), {root, value}); - } - } - } - - assignFixedType(typeVar, contextualType); - return true; -} - bool ConstraintSystem::resolvePackExpansion(TypeVariableType *typeVar, Type contextualType) { assert(typeVar->getImpl().isPackExpansion()); @@ -12237,8 +12173,6 @@ ConstraintSystem::simplifyKeyPathConstraint( TypeMatchOptions flags, ConstraintLocatorBuilder locator) { auto subflags = getDefaultDecompositionOptions(flags); - // The constraint ought to have been anchored on a KeyPathExpr. - auto keyPath = castToExpr(locator.getAnchor()); keyPathTy = getFixedTypeRecursive(keyPathTy, /*want rvalue*/ true); auto formUnsolved = [&]() -> SolutionKind { @@ -12254,49 +12188,48 @@ ConstraintSystem::simplifyKeyPathConstraint( if (keyPathTy->isTypeVariableOrMember()) return formUnsolved(); - auto tryMatchRootAndValueFromType = [&](Type type) -> bool { - Type boundRoot = Type(), boundValue = Type(); + auto tryMatchRootAndValueFromContextualType = [&](Type contextualTy) -> bool { + Type contextualRootTy = Type(), contextualValueTy = Type(); + + // Placeholders are only allowed in the diagnostic mode so it's + // okay to simply return `true` here. + if (contextualTy->isPlaceholder()) + return true; + + // If there are no other options the solver might end up picking + // `AnyKeyPath` or `PartialKeyPath` based on a contextual conversion. + // This is an error during normal type-checking but okay in + // diagnostic mode because root and value are allowed to be holes. + if (contextualTy->isAnyKeyPath() || contextualTy->isPartialKeyPath()) + return shouldAttemptFixes(); - if (auto bgt = type->getAs()) { + if (auto bgt = contextualTy->getAs()) { // We can get root and value from a concrete key path type. - if (bgt->isKeyPath() || - bgt->isWritableKeyPath() || - bgt->isReferenceWritableKeyPath()) { - boundRoot = bgt->getGenericArgs()[0]; - boundValue = bgt->getGenericArgs()[1]; - } else { - return false; - } + assert(bgt->isKeyPath() || bgt->isWritableKeyPath() || + bgt->isReferenceWritableKeyPath()); + + contextualRootTy = bgt->getGenericArgs()[0]; + contextualValueTy = bgt->getGenericArgs()[1]; } - if (auto fnTy = type->getAs()) { + if (auto fnTy = contextualTy->getAs()) { assert(fnTy->getParams().size() == 1); // Match up the root and value types to the function's param and return // types. Note that we're using the type of the parameter as referenced // from inside the function body as we'll be transforming the code into: // { root in root[keyPath: kp] }. - boundRoot = fnTy->getParams()[0].getParameterType(); - boundValue = fnTy->getResult(); - - // Key paths never throw, so if the function has a thrown error type - // that is a type variable, infer it to be Never. - if (auto thrownError = fnTy->getThrownError()) { - if (thrownError->isTypeVariableOrMember()) { - (void)matchTypes( - thrownError, getASTContext().getNeverType(), - ConstraintKind::Equal, TMF_GenerateConstraints, locator); - } - } + contextualRootTy = fnTy->getParams()[0].getParameterType(); + contextualValueTy = fnTy->getResult(); } - if (boundRoot && - matchTypes(rootTy, boundRoot, ConstraintKind::Bind, subflags, + assert(contextualRootTy && contextualValueTy); + + if (matchTypes(rootTy, contextualRootTy, ConstraintKind::Bind, subflags, locator.withPathElement(ConstraintLocator::KeyPathRoot)) .isFailure()) return false; - if (boundValue && - matchTypes(valueTy, boundValue, ConstraintKind::Bind, subflags, + if (matchTypes(valueTy, contextualValueTy, ConstraintKind::Bind, subflags, locator.withPathElement(ConstraintLocator::KeyPathValue)) .isFailure()) return false; @@ -12307,6 +12240,18 @@ ConstraintSystem::simplifyKeyPathConstraint( // If key path has to be converted to a function, let's check that // the contextual type has precisely one parameter. if (auto *fnTy = keyPathTy->getAs()) { + increaseScore(SK_FunctionConversion, locator); + + // Key paths never throw, so if the function has a thrown error type + // that is a type variable, infer it to be Never. + if (auto thrownError = fnTy->getThrownError()) { + if (thrownError->isTypeVariableOrMember()) { + (void)matchTypes(thrownError, getASTContext().getNeverType(), + ConstraintKind::Equal, TMF_GenerateConstraints, + locator); + } + } + if (fnTy->getParams().size() != 1) { if (!shouldAttemptFixes()) return SolutionKind::Error; @@ -12324,8 +12269,7 @@ ConstraintSystem::simplifyKeyPathConstraint( // If we have a hole somewhere in the key path, the solver won't be able to // infer the key path type. So let's just assume this is solved. if (shouldAttemptFixes()) { - if (keyPathTy->isPlaceholder()) - return SolutionKind::Solved; + auto keyPath = castToExpr(locator.getAnchor()); if (hasFixFor(getConstraintLocator(keyPath), FixKind::AllowKeyPathWithoutComponents)) @@ -12336,60 +12280,9 @@ ConstraintSystem::simplifyKeyPathConstraint( return SolutionKind::Solved; } - // If we're fixed to a bound generic type, trying harvesting context from it. - // However, we don't want a solution that fixes the expression type to - // PartialKeyPath; we'd rather that be represented using an upcast conversion. - if (!tryMatchRootAndValueFromType(keyPathTy)) - return SolutionKind::Error; - - bool isValid; - llvm::Optional capability; - - std::tie(isValid, capability) = inferKeyPathLiteralCapability(keyPath); - - // If key path is invalid, let's not don't attempt match capabilities. - if (!isValid) - return shouldAttemptFixes() ? SolutionKind::Solved : SolutionKind::Error; - - // If key path is valid but not yet sufficiently resolved, let's delay - // capability checking. - if (!capability) - return formUnsolved(); - - // Resolve the type. - NominalTypeDecl *kpDecl; - switch (*capability) { - case KeyPathCapability::ReadOnly: - kpDecl = getASTContext().getKeyPathDecl(); - break; - - case KeyPathCapability::Writable: - kpDecl = getASTContext().getWritableKeyPathDecl(); - break; - - case KeyPathCapability::ReferenceWritable: - kpDecl = getASTContext().getReferenceWritableKeyPathDecl(); - break; - } - - // FIXME: Allow the type to be upcast if the type system has a concrete - // KeyPath type assigned to the expression already. - if (auto keyPathBGT = keyPathTy->getAs()) { - if (keyPathBGT->isKeyPath()) - kpDecl = getASTContext().getKeyPathDecl(); - else if (keyPathBGT->isWritableKeyPath() && - *capability >= KeyPathCapability::Writable) - kpDecl = getASTContext().getWritableKeyPathDecl(); - } - - if (keyPathTy->is()) { - increaseScore(SK_FunctionConversion, locator); - return SolutionKind::Solved; - } - - auto resolvedKPTy = BoundGenericType::get(kpDecl, nullptr, {rootTy, valueTy}); - return matchTypes(resolvedKPTy, keyPathTy, ConstraintKind::Bind, subflags, - locator); + return tryMatchRootAndValueFromContextualType(keyPathTy) + ? SolutionKind::Solved + : SolutionKind::Error; } ConstraintSystem::SolutionKind diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index 12fe4d280709f..e9f3b98545b18 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -7456,6 +7456,9 @@ ConstraintSystem::inferKeyPathLiteralCapability(KeyPathExpr *keyPath) { return std::make_pair(true, capability); }; + if (keyPath->hasSingleInvalidComponent()) + return fail(); + auto capability = KeyPathCapability::Writable; for (unsigned i : indices(keyPath->getComponents())) { auto &component = keyPath->getComponents()[i]; diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 61654833f9176..eaf4292834f57 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -5379,16 +5379,23 @@ static void maybeDiagnoseCallToKeyValueObserveMethod(const Expr *E, return; SmallVector keyPathArgs; auto *args = expr->getArgs(); + + auto isKeyPathLiteral = [&](Expr *argExpr) -> KeyPathExpr * { + if (auto *DTBE = getAsExpr(argExpr)) + argExpr = DTBE->getSubExpr(); + return getAsExpr(argExpr); + }; + if (fn->getModuleContext()->getName() == C.Id_Foundation && fn->getName().isCompoundName("observe", {"", "options", "changeHandler"})) { - if (auto keyPathArg = dyn_cast(args->getExpr(0))) { + if (auto keyPathArg = isKeyPathLiteral(args->getExpr(0))) { keyPathArgs.push_back(keyPathArg); } } else if (fn->getAttrs() .hasSemanticsAttr(semantics::KEYPATH_MUST_BE_VALID_FOR_KVO)) { - for (const auto& arg: *args) { - if (auto keyPathArg = dyn_cast(arg.getExpr())) { + for (auto *argExpr : args->getArgExprs()) { + if (auto keyPathArg = isKeyPathLiteral(argExpr)) { keyPathArgs.push_back(keyPathArg); } } diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index ff3e2b10a6c97..14b539565b738 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -144,6 +144,10 @@ bool TypeVariableType::Implementation::isKeyPathType() const { return locator && locator->isKeyPathType(); } +bool TypeVariableType::Implementation::isKeyPathRoot() const { + return locator && locator->isKeyPathRoot(); +} + bool TypeVariableType::Implementation::isKeyPathValue() const { return locator && locator->isKeyPathValue(); } diff --git a/test/Constraints/tuple.swift b/test/Constraints/tuple.swift index 10a9be2bf81c4..b7a856d358f5f 100644 --- a/test/Constraints/tuple.swift +++ b/test/Constraints/tuple.swift @@ -363,7 +363,7 @@ func testTupleLabelMismatchFuncConversion(fn1: @escaping ((x: Int, y: Int)) -> V } func testTupleLabelMismatchKeyPath() { + // FIXME: The warning should be upgraded to an error for key paths. let _: KeyPath<(x: Int, y: Int), Int> = \(a: Int, b: Int).x -// expected-error@-1 {{key path with root type '(x: Int, y: Int)' cannot be applied to a base of type '(a: Int, b: Int)'}} -// expected-error@-2 {{value of tuple type '(a: Int, b: Int)' has no member 'x'}} + // expected-warning@-1 {{tuple conversion from '(a: Int, b: Int)' to '(x: Int, y: Int)' mismatches labels}} } diff --git a/test/expr/unary/keypath/keypath.swift b/test/expr/unary/keypath/keypath.swift index b26c0090867e2..250db6867f4ab 100644 --- a/test/expr/unary/keypath/keypath.swift +++ b/test/expr/unary/keypath/keypath.swift @@ -692,7 +692,8 @@ func testSubtypeKeypathClass(_ keyPath: ReferenceWritableKeyPath) { func testSubtypeKeypathProtocol(_ keyPath: ReferenceWritableKeyPath) { testSubtypeKeypathProtocol(\Base.i) - // expected-error@-1 {{key path with root type 'any PP' cannot be applied to a base of type 'Base'}} + // expected-error@-1 {{cannot convert value of type 'ReferenceWritableKeyPath' to expected argument type 'ReferenceWritableKeyPath'}} + // expected-note@-2 {{arguments to generic parameter 'Root' ('Base' and 'any PP') are expected to be equal}} } // rdar://problem/32057712 @@ -913,7 +914,9 @@ func testKeyPathHole() { func f(_ i: Int) {} f(\.x) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}} {{6-6=<#Root#>}} + // expected-error@-1 {{cannot convert value of key path type to expected argument type 'Int'}} f(\.x.y) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}} {{6-6=<#Root#>}} + // expected-error@-1 {{cannot convert value of key path type to expected argument type 'Int'}} func provideValueButNotRoot(_ fn: (T) -> String) {} provideValueButNotRoot(\.x) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}} diff --git a/validation-test/Sema/SwiftUI/rdar84580119.swift b/validation-test/Sema/SwiftUI/rdar84580119.swift index 9e18c59a67f46..87fc85eaf33f2 100644 --- a/validation-test/Sema/SwiftUI/rdar84580119.swift +++ b/validation-test/Sema/SwiftUI/rdar84580119.swift @@ -16,6 +16,7 @@ extension EnvironmentValues { set { self[\.MyHorizontalAlignmentEnvironmentKey.self] = newValue } // expected-error@-1 {{generic parameter 'K' could not be inferred}} // expected-error@-2 {{cannot infer key path type from context; consider explicitly specifying a root type}} + // expected-error@-3 {{cannot convert value of key path type to expected argument type 'K.Type'}} } }