コード例 #1
0
static Type deriveCaseIterable_AllCases(DerivedConformance &derived) {
  // enum SomeEnum : CaseIterable {
  //   @derived
  //   typealias AllCases = [SomeEnum]
  // }
  auto *rawInterfaceType = computeAllCasesType(cast<EnumDecl>(derived.Nominal));
  return derived.getConformanceContext()->mapTypeIntoContext(rawInterfaceType);
}
コード例 #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;
}
コード例 #3
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);
}
コード例 #4
0
/// Returns whether the given type is valid for synthesizing {En,De}codable.
///
/// Checks to see whether the given type has a valid \c CodingKeys enum, and if
/// not, attempts to synthesize one for it.
///
/// \param requirement The requirement we want to synthesize.
static bool canSynthesize(DerivedConformance &derived, ValueDecl *requirement) {
  // Before we attempt to look up (or more importantly, synthesize) a CodingKeys
  // entity on target, we need to make sure the type is otherwise valid.
  //
  // If we are synthesizing Decodable and the target is a class with a
  // superclass, our synthesized init(from:) will need to call either
  // super.init(from:) or super.init() depending on whether the superclass is
  // Decodable itself.
  //
  // If the required initializer is not available, we shouldn't attempt to
  // synthesize CodingKeys.
  auto &tc = derived.TC;
  ASTContext &C = tc.Context;
  auto proto = derived.Protocol;
  auto *classDecl = dyn_cast<ClassDecl>(derived.Nominal);
  if (proto->isSpecificProtocol(KnownProtocolKind::Decodable) && classDecl) {
    if (auto *superclassDecl = classDecl->getSuperclassDecl()) {
      DeclName memberName;
      auto superType = superclassDecl->getDeclaredInterfaceType();
      if (tc.conformsToProtocol(superType, proto, superclassDecl,
                                ConformanceCheckFlags::Used)) {
        // super.init(from:) must be accessible.
        memberName = cast<ConstructorDecl>(requirement)->getFullName();
      } else {
        // super.init() must be accessible.
        // Passing an empty params array constructs a compound name with no
        // arguments (as opposed to a simple name when omitted).
        memberName = DeclName(C, DeclBaseName::createConstructor(),
                              ArrayRef<Identifier>());
      }

      auto result = tc.lookupMember(superclassDecl, superType, memberName);

      if (result.empty()) {
        // No super initializer for us to call.
        tc.diagnose(superclassDecl, diag::decodable_no_super_init_here,
                    requirement->getFullName(), memberName);
        return false;
      } else if (result.size() > 1) {
        // There are multiple results for this lookup. We'll end up producing a
        // diagnostic later complaining about duplicate methods (if we haven't
        // already), so just bail with a general error.
        return false;
      } else {
        auto *initializer =
          cast<ConstructorDecl>(result.front().getValueDecl());
        auto conformanceDC = derived.getConformanceContext();
        if (!initializer->isDesignatedInit()) {
          // We must call a superclass's designated initializer.
          tc.diagnose(initializer,
                      diag::decodable_super_init_not_designated_here,
                      requirement->getFullName(), memberName);
          return false;
        } else if (!initializer->isAccessibleFrom(conformanceDC)) {
          // Cannot call an inaccessible method.
          auto accessScope = initializer->getFormalAccessScope(conformanceDC);
          tc.diagnose(initializer, diag::decodable_inaccessible_super_init_here,
                      requirement->getFullName(), memberName,
                      accessScope.accessLevelForDiagnostics());
          return false;
        } else if (initializer->getFailability() != OTK_None) {
          // We can't call super.init() if it's failable, since init(from:)
          // isn't failable.
          tc.diagnose(initializer, diag::decodable_super_init_is_failable_here,
                      requirement->getFullName(), memberName);
          return false;
        }
      }
    }
  }

  // If the target already has a valid CodingKeys enum, we won't need to
  // synthesize one.
  auto validity = hasValidCodingKeysEnum(derived);

  // We found a type, but it wasn't valid.
  if (!validity.isValid)
    return false;

  // We can try to synthesize a type here.
  if (!validity.hasType) {
    auto *synthesizedEnum = synthesizeCodingKeysEnum(derived);
    if (!synthesizedEnum)
      return false;
  }

  return true;
}