コード例 #1
0
/// Returns whether the given type has a valid nested \c CodingKeys enum.
///
/// If the type has an invalid \c CodingKeys entity, produces diagnostics to
/// complain about the error. In this case, the error result will be true -- in
/// the case where we don't have a valid CodingKeys enum and have produced
/// diagnostics here, we don't want to then attempt to synthesize a CodingKeys
/// enum.
///
/// \returns A \c CodingKeysValidity value representing the result of the check.
static CodingKeysValidity hasValidCodingKeysEnum(DerivedConformance &derived) {
  auto &tc = derived.TC;
  auto &C = tc.Context;
  auto codingKeysDecls =
      derived.Nominal->lookupDirect(DeclName(C.Id_CodingKeys));
  if (codingKeysDecls.empty())
    return CodingKeysValidity(/*hasType=*/false, /*isValid=*/true);

  // Only ill-formed code would produce multiple results for this lookup.
  // This would get diagnosed later anyway, so we're free to only look at the
  // first result here.
  auto result = codingKeysDecls.front();

  auto *codingKeysTypeDecl = dyn_cast<TypeDecl>(result);
  if (!codingKeysTypeDecl) {
    tc.diagnose(result->getLoc(),
                diag::codable_codingkeys_type_is_not_an_enum_here,
                derived.getProtocolType());
    return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false);
  }

  // If the decl hasn't been validated yet, do so.
  tc.validateDecl(codingKeysTypeDecl);

  // CodingKeys may be a typealias. If so, follow the alias to its canonical
  // type.
  auto codingKeysType = codingKeysTypeDecl->getDeclaredInterfaceType();
  if (isa<TypeAliasDecl>(codingKeysTypeDecl))
    codingKeysTypeDecl = codingKeysType->getAnyNominal();

  // Ensure that the type we found conforms to the CodingKey protocol.
  auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey);
  if (!tc.conformsToProtocol(codingKeysType, codingKeyProto,
                             derived.getConformanceContext(),
                             ConformanceCheckFlags::Used)) {
    // If CodingKeys is a typealias which doesn't point to a valid nominal type,
    // codingKeysTypeDecl will be nullptr here. In that case, we need to warn on
    // the location of the usage, since there isn't an underlying type to
    // diagnose on.
    SourceLoc loc = codingKeysTypeDecl ?
                    codingKeysTypeDecl->getLoc() :
                    cast<TypeDecl>(result)->getLoc();

    tc.diagnose(loc, diag::codable_codingkeys_type_does_not_conform_here,
                derived.getProtocolType());

    return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false);
  }

  // CodingKeys must be an enum for synthesized conformance.
  auto *codingKeysEnum = dyn_cast<EnumDecl>(codingKeysTypeDecl);
  if (!codingKeysEnum) {
    tc.diagnose(codingKeysTypeDecl->getLoc(),
                diag::codable_codingkeys_type_is_not_an_enum_here,
                derived.getProtocolType());
    return CodingKeysValidity(/*hasType=*/true, /*isValid=*/false);
  }

  bool valid = validateCodingKeysEnum(derived, codingKeysEnum);
  return CodingKeysValidity(/*hasType=*/true, /*isValid=*/valid);
}
コード例 #2
0
/// Validates the given CodingKeys enum decl by ensuring its cases are a 1-to-1
/// match with the stored vars of the given type.
///
/// \param codingKeysDecl The \c CodingKeys enum decl to validate.
static bool validateCodingKeysEnum(DerivedConformance &derived,
                                   EnumDecl *codingKeysDecl) {
  auto &tc = derived.TC;
  auto conformanceDC = derived.getConformanceContext();

  // Look through all var decls in the given type.
  // * Filter out lazy/computed vars.
  // * Filter out ones which are present in the given decl (by name).
  //
  // If any of the entries in the CodingKeys decl are not present in the type
  // by name, then this decl doesn't match.
  // If there are any vars left in the type which don't have a default value
  // (for Decodable), then this decl doesn't match.

  // Here we'll hold on to properties by name -- when we've validated a property
  // against its CodingKey entry, it will get removed.
  llvm::SmallDenseMap<Identifier, VarDecl *, 8> properties;
  for (auto *varDecl :
       derived.Nominal->getStoredProperties(/*skipInaccessible=*/true)) {
    if (varDecl->getAttrs().hasAttribute<LazyAttr>())
      continue;

    properties[varDecl->getName()] = varDecl;
  }

  bool propertiesAreValid = true;
  for (auto elt : codingKeysDecl->getAllElements()) {
    auto it = properties.find(elt->getName());
    if (it == properties.end()) {
      tc.diagnose(elt->getLoc(), diag::codable_extraneous_codingkey_case_here,
                  elt->getName());
      // TODO: Investigate typo-correction here; perhaps the case name was
      //       misspelled and we can provide a fix-it.
      propertiesAreValid = false;
      continue;
    }

    // We have a property to map to. Ensure it's {En,De}codable.
    auto conformance =
        varConformsToCodable(tc, conformanceDC, it->second, derived.Protocol);
    switch (conformance) {
      case Conforms:
        // The property was valid. Remove it from the list.
        properties.erase(it);
        break;

      case DoesNotConform:
        tc.diagnose(it->second->getLoc(),
                    diag::codable_non_conforming_property_here,
                    derived.getProtocolType(), it->second->getType());
        LLVM_FALLTHROUGH;

      case TypeNotValidated:
        // We don't produce a diagnostic for a type which failed to validate.
        // This will produce a diagnostic elsewhere anyway.
        propertiesAreValid = false;
        continue;
    }
  }

  if (!propertiesAreValid)
    return false;

  // If there are any remaining properties which the CodingKeys did not cover,
  // we can skip them on encode. On decode, though, we can only skip them if
  // they have a default value.
  if (!properties.empty() &&
      derived.Protocol->isSpecificProtocol(KnownProtocolKind::Decodable)) {
    for (auto it = properties.begin(); it != properties.end(); ++it) {
      // If the var is default initializable, then it need not have an explicit
      // initial value.
      auto *varDecl = it->second;
      if (auto pbd = varDecl->getParentPatternBinding()) {
        if (pbd->isDefaultInitializable())
          continue;
      }

      if (varDecl->getParentInitializer())
        continue;

      // The var was not default initializable, and did not have an explicit
      // initial value.
      propertiesAreValid = false;
      tc.diagnose(it->second->getLoc(), diag::codable_non_decoded_property_here,
                  derived.getProtocolType(), it->first);
    }
  }

  return propertiesAreValid;
}