Optional<Type> TypeChecker::checkObjCKeyPathExpr(DeclContext *dc,
                                                 ObjCKeyPathExpr *expr,
                                                 bool requireResultType) {
  // If there is already a semantic expression, do nothing.
  if (expr->getSemanticExpr() && !requireResultType) return None;

  // #keyPath only makes sense when we have the Objective-C runtime.
  if (!Context.LangOpts.EnableObjCInterop) {
    diagnose(expr->getLoc(), diag::expr_keypath_no_objc_runtime);

    expr->setSemanticExpr(
      new (Context) StringLiteralExpr("", expr->getSourceRange(),
                                      /*Implicit=*/true));
    return None;
  }

  // The key path string we're forming.
  SmallString<32> keyPathScratch;
  llvm::raw_svector_ostream keyPathOS(keyPathScratch);

  // Captures the state of semantic resolution.
  enum State {
    Beginning,
    ResolvingType,
    ResolvingProperty,
    ResolvingArray,
    ResolvingSet,
    ResolvingDictionary,
  } state = Beginning;
  
  /// Determine whether we are currently resolving a property.
  auto isResolvingProperty = [&] {
    switch (state) {
    case Beginning:
    case ResolvingType:
      return false;

    case ResolvingProperty:
    case ResolvingArray:
    case ResolvingSet:
    case ResolvingDictionary:
      return true;
    }
  };

  // The type of AnyObject, which is used whenever we don't have
  // sufficient type information.
  Type anyObjectType;
  if (auto anyObject = Context.getProtocol(KnownProtocolKind::AnyObject)) {
    validateDecl(anyObject);
    anyObjectType = anyObject->getDeclaredInterfaceType();
  } else {
    diagnose(expr->getLoc(), diag::stdlib_anyobject_not_found);
    return None;
  }

  // Local function to update the state after we've resolved a
  // component.
  Type currentType;
  auto updateState = [&](bool isProperty, Type newType) {
    // Strip off optionals.
    newType = newType->lookThroughAllAnyOptionalTypes();

    // If updating to a type, just set the new type; there's nothing
    // more to do.
    if (!isProperty) {
      assert(state == Beginning || state == ResolvingType);
      state = ResolvingType;
      currentType = newType;
      return;
    }

    // We're updating to a property. Determine whether we're looking
    // into a bridged Swift collection of some sort.
    if (auto boundGeneric = newType->getAs<BoundGenericType>()) {
      auto nominal = boundGeneric->getDecl();

      // Array<T>
      if (nominal == Context.getArrayDecl()) {
        // Further lookups into the element type.
        state = ResolvingArray;
        currentType = boundGeneric->getGenericArgs()[0];
        return;
      }

      // Set<T>
      if (nominal == Context.getSetDecl()) {
        // Further lookups into the element type.
        state = ResolvingSet;
        currentType = boundGeneric->getGenericArgs()[0];
        return;
      }

      // Dictionary<K, V>
      if (nominal == Context.getDictionaryDecl()) {
        // Key paths look into the keys of a dictionary; further
        // lookups into the value type.
        state = ResolvingDictionary;
        currentType = boundGeneric->getGenericArgs()[1];
        return;
      }
    }

    // Determine whether we're looking into a Foundation collection.
    if (auto classDecl = newType->getClassOrBoundGenericClass()) {
      if (classDecl->isObjC() && classDecl->hasClangNode()) {
        SmallString<32> scratch;
        StringRef objcClassName = classDecl->getObjCRuntimeName(scratch);

        // NSArray
        if (objcClassName == "NSArray") {
          // The element type is unknown, so use AnyObject.
          state = ResolvingArray;
          currentType = anyObjectType;
          return;
        }

        // NSSet
        if (objcClassName == "NSSet") {
          // The element type is unknown, so use AnyObject.
          state = ResolvingSet;
          currentType = anyObjectType;
          return;
        }

        // NSDictionary
        if (objcClassName == "NSDictionary") {
          // Key paths look into the keys of a dictionary; there's no
          // type to help us here.
          state = ResolvingDictionary;
          currentType = anyObjectType;
          return;
        }
      }
    }

    // It's just a property.
    state = ResolvingProperty;
    currentType = newType;
  };
  
  // Local function to perform name lookup for the current index.
  auto performLookup = [&](unsigned idx, Identifier componentName,
                           SourceLoc componentNameLoc) -> LookupResult {
    if (state == Beginning)
      return lookupUnqualified(dc, componentName, componentNameLoc);

    assert(currentType && "Non-beginning state must have a type");

    // Determine the type in which the lookup should occur. If we have
    // a bridged value type, this will be the Objective-C class to
    // which it is bridged.
    Type lookupType;
    if (auto bridgedClass = Context.getBridgedToObjC(dc, currentType))
      lookupType = bridgedClass;
    else
      lookupType = currentType;

    // Look for a member with the given name within this type.
    return lookupMember(dc, lookupType, componentName);
  };

  // Local function to print a component to the string.
  bool needDot = false;
  auto printComponent = [&](Identifier component) {
    if (needDot)
      keyPathOS << ".";
    else
      needDot = true;

    keyPathOS << component.str();
  };

  bool isInvalid = false;
  for (unsigned idx : range(expr->getNumComponents())) {
    auto componentName = expr->getComponentName(idx);
    auto componentNameLoc = expr->getComponentNameLoc(idx);

    // If we are resolving into a dictionary, any component is
    // well-formed because the keys are unknown dynamically.
    if (state == ResolvingDictionary) {
      // Just print the component unchanged; there's no checking we
      // can do here.
      printComponent(componentName);

      // From here, we're resolving a property. Use the current type.
      updateState(/*isProperty=*/true, currentType);

      continue;
    }

    // Look for this component.
    LookupResult lookup = performLookup(idx, componentName, componentNameLoc);

    // If we didn't find anything, try to apply typo-correction.
    bool resultsAreFromTypoCorrection = false;
    if (!lookup) {
      performTypoCorrection(dc, DeclRefKind::Ordinary, currentType,
                            componentName, componentNameLoc,
                            (currentType ? defaultMemberTypeLookupOptions
                                         : defaultUnqualifiedLookupOptions),
                            lookup);

      if (currentType)
        diagnose(componentNameLoc, diag::could_not_find_type_member,
                 currentType, componentName);
      else
        diagnose(componentNameLoc, diag::use_unresolved_identifier,
                 componentName, false);

      // Note all the correction candidates.
      for (auto &result : lookup) {
        noteTypoCorrection(componentName, DeclNameLoc(componentNameLoc),
                           result);
      }

      isInvalid = true;
      if (!lookup) break;

      // Remember that these are from typo correction.
      resultsAreFromTypoCorrection = true;
    }

    // If we have more than one result, filter out unavailable or
    // obviously unusable candidates.
    if (lookup.size() > 1) {
      lookup.filter([&](LookupResult::Result result) -> bool {
          // Drop unavailable candidates.
          if (result->getAttrs().isUnavailable(Context))
            return false;

          // Drop non-property, non-type candidates.
          if (!isa<VarDecl>(result.Decl) && !isa<TypeDecl>(result.Decl))
            return false;

          return true;
      });
    }

    // If we *still* have more than one result, fail.
    if (lookup.size() > 1) {
      // Don't diagnose ambiguities if the results are from typo correction.
      if (resultsAreFromTypoCorrection)
        break;

      if (currentType)
        diagnose(componentNameLoc, diag::ambiguous_member_overload_set,
                 componentName);
      else
        diagnose(componentNameLoc, diag::ambiguous_decl_ref,
                 componentName);

      for (auto result : lookup) {
        diagnose(result, diag::decl_declared_here, result->getFullName());
      }
      isInvalid = true;
      break;
    }

    auto found = lookup.front().Decl;

    // Handle property references.
    if (auto var = dyn_cast<VarDecl>(found)) {
      validateDecl(var);

      // Resolve this component to the variable we found.
      expr->resolveComponent(idx, var);
      updateState(/*isProperty=*/true, var->getType()->getRValueObjectType());

      // Check that the property is @objc.
      if (!var->isObjC()) {
        diagnose(componentNameLoc, diag::expr_keypath_non_objc_property,
                 componentName);
        if (var->getLoc().isValid() && var->getDeclContext()->isTypeContext()) {
          diagnose(var, diag::make_decl_objc,
                   var->getDescriptiveKind())
            .fixItInsert(var->getAttributeInsertionLoc(false),
                         "@objc ");
        }
      } else {
        // FIXME: Warn about non-KVC-compliant getter/setter names?
      }

      // Print the Objective-C property name.
      printComponent(var->getObjCPropertyName());
      continue;
    }

    // Handle type references.
    if (auto type = dyn_cast<TypeDecl>(found)) {
      // We cannot refer to a type via a property.
      if (isResolvingProperty()) {
        diagnose(componentNameLoc, diag::expr_keypath_type_of_property,
                 componentName, currentType);
        isInvalid = true;
        break;
      }

      // We cannot refer to a generic type.
      if (type->getDeclaredInterfaceType()->hasTypeParameter()) {
        diagnose(componentNameLoc, diag::expr_keypath_generic_type,
                 componentName);
        isInvalid = true;
        break;
      }

      Type newType;
      if (currentType && !currentType->isAnyObject()) {
        newType = currentType->getTypeOfMember(dc->getParentModule(), type,
                                               this);
      } else {
        newType = type->getDeclaredInterfaceType();
      }
      if (!newType) {
        isInvalid = true;
        break;
      }

      updateState(/*isProperty=*/false, newType);
      continue;
    }

    // Declarations that cannot be part of a key-path.
    diagnose(componentNameLoc, diag::expr_keypath_not_property,
             found->getDescriptiveKind(), found->getFullName());
    isInvalid = true;
    break;
  }

  // Check for an empty key-path string.
  auto keyPathString = keyPathOS.str();
  if (keyPathString.empty() && !isInvalid)
    diagnose(expr->getLoc(), diag::expr_keypath_empty);

  // Set the semantic expression.
  if (!expr->getSemanticExpr()) {
    expr->setSemanticExpr(
      new (Context) StringLiteralExpr(Context.AllocateCopy(keyPathString),
                                      expr->getSourceRange(),
                                      /*Implicit=*/true));
  }

  if (!currentType) return None;
  return currentType;
}
Exemple #2
0
Type CompleteGenericTypeResolver::resolveDependentMemberType(
                                    Type baseTy,
                                    DeclContext *DC,
                                    SourceRange baseRange,
                                    ComponentIdentTypeRepr *ref) {
  // Resolve the base to a potential archetype.
  auto basePA =
    Builder.resolveArchetype(baseTy,
                             ArchetypeResolutionKind::CompleteWellFormed);
  assert(basePA && "Missing potential archetype for base");

  // Retrieve the potential archetype for the nested type.
  auto nestedPA =
    basePA->getNestedType(ref->getIdentifier(),
                          ArchetypeResolutionKind::CompleteWellFormed,
                          Builder);

  // If there was no such nested type, produce an error.
  if (!nestedPA) {
    // Perform typo correction.
    LookupResult corrections;
    TC.performTypoCorrection(DC, DeclRefKind::Ordinary,
                             MetatypeType::get(baseTy),
                             ref->getIdentifier(), ref->getIdLoc(),
                             NameLookupFlags::ProtocolMembers,
                             corrections, &Builder);

    // Filter out non-types.
    corrections.filter([](const LookupResultEntry &result) {
      return isa<TypeDecl>(result.getValueDecl());
    });

    // Check whether we have a single type result.
    auto singleType = corrections.getSingleTypeResult();

    // If we don't have a single result, complain and fail.
    if (!singleType) {
      Identifier name = ref->getIdentifier();
      SourceLoc nameLoc = ref->getIdLoc();
      TC.diagnose(nameLoc, diag::invalid_member_type, name, baseTy)
        .highlight(baseRange);
      for (const auto &suggestion : corrections)
        TC.noteTypoCorrection(name, DeclNameLoc(nameLoc),
                              suggestion.getValueDecl());

      return ErrorType::get(TC.Context);
    }

    // We have a single type result. Suggest it.
    TC.diagnose(ref->getIdLoc(), diag::invalid_member_type_suggest,
                baseTy, ref->getIdentifier(),
                singleType->getBaseName().getIdentifier())
      .fixItReplace(ref->getIdLoc(),
                    singleType->getBaseName().userFacingName());

    // Correct to the single type result.
    ref->overwriteIdentifier(singleType->getBaseName().getIdentifier());
    ref->setValue(singleType, nullptr);
  } else if (auto assocType = nestedPA->getResolvedAssociatedType()) {
    ref->setValue(assocType, nullptr);
  } else {
    assert(nestedPA->getConcreteTypeDecl());
    ref->setValue(nestedPA->getConcreteTypeDecl(), nullptr);
  }

  // If the nested type has been resolved to an associated type, use it.
  if (auto assocType = dyn_cast<AssociatedTypeDecl>(ref->getBoundDecl())) {
    return DependentMemberType::get(baseTy, assocType);
  }

  // Otherwise, the nested type comes from a concrete type. Substitute the
  // base type into it.
  auto concrete = ref->getBoundDecl();
  TC.validateDeclForNameLookup(concrete);
  if (!concrete->hasInterfaceType())
    return ErrorType::get(TC.Context);
  if (baseTy->isTypeParameter()) {
    if (auto proto =
          concrete->getDeclContext()
            ->getAsProtocolOrProtocolExtensionContext()) {
      TC.validateDecl(proto);
      auto subMap = SubstitutionMap::getProtocolSubstitutions(
                      proto, baseTy, ProtocolConformanceRef(proto));
      return concrete->getDeclaredInterfaceType().subst(subMap);
    }

    if (auto superclass = basePA->getSuperclass()) {
      return superclass->getTypeOfMember(
                                       DC->getParentModule(), concrete,
                                       concrete->getDeclaredInterfaceType());
    }

    llvm_unreachable("shouldn't have a concrete decl here");
  }

  return TC.substMemberTypeWithBase(DC->getParentModule(), concrete, baseTy);
}