Skip to content

Codable synthesis: excluded vars shouldn’t need explicit value assignments #11854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 11, 2017

Conversation

itaiferber
Copy link
Contributor

What's in this pull request?
Properties of Codable types which are optional and excluded via the CodingKeys enum should not require an explicit nil assignment, since in all other cases, optional vars get a default value of nil. The following code should get correct synthesis:

class Foo : Codable {
    // These all get a default value of `nil`.
    var p1: String?
    var p2: String!
    var p3: AnyHashable?
    var p4: AnyHashable!

    private enum CodingKeys : String, CodingKey {
        case p1, p2
    }
}

Instead, p3 and p4 currently prevent synthesis because they don't have an explicit nil assignment.

Properties of Codable types which are optional and excluded via the CodingKeys enum should not require an explicit nil assignment, since in all other cases, optional vars get a default value of nil.
@itaiferber
Copy link
Contributor Author

@swift-ci Please test

@itaiferber itaiferber merged commit 196fe68 into swiftlang:master Sep 11, 2017
if (varDecl->hasType()) {
auto varTypeDecl = varDecl->getType()->getAnyNominal();
if (varTypeDecl == tc.Context.getOptionalDecl() ||
varTypeDecl == tc.Context.getImplicitlyUnwrappedOptionalDecl())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have TypeBase::getAnyOptionalObjectType for this, but really I'm leery of putting this check in more than one place. Is there any way to reuse the logic in TypeChecker::addImplicitConstructors?

Copy link
Contributor Author

@itaiferber itaiferber Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrose-apple That's likely a smarter solution. Are you interested in copying the logic into here, or factoring it out for reuse (e.g. adding isDefaultInitializable or similar to PatternBindingDecl)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect it belongs on VarDecl rather than PatternBindingDecl, but the latter sounds good to me. Even if it's mostly just "is optional and is not immutable".

Copy link
Contributor Author

@itaiferber itaiferber Sep 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrose-apple I'll adapt this, then. I mention PatternBindingDecl only because the logic in TypeChecker::addImplicitConstructors revolves around that:

if (auto pbd = dyn_cast<PatternBindingDecl>(member)) {
  if (pbd->hasStorage() && !pbd->isStatic() && !pbd->isImplicit())
    for (auto entry : pbd->getPatternList()) {
      if (entry.getInit()) continue;

      // If one of the bound variables is @NSManaged, go ahead no matter
      // what.
      bool CheckDefaultInitializer = true;
      entry.getPattern()->forEachVariable([&](VarDecl *vd) {
        if (vd->getAttrs().hasAttribute<NSManagedAttr>())
          CheckDefaultInitializer = false;
      });
      
      // If we cannot default initialize the property, we cannot
      // synthesize a default initializer for the class.
      if (CheckDefaultInitializer && !isDefaultInitializable(pbd))
        SuppressDefaultInitializer = true;
    }
  continue;
}

(isDefaultInitializable takes a PatternBindingDecl)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, though I guess the implementation we really care about takes a TypeRepr *:

static bool isDefaultInitializable(TypeRepr *typeRepr) {
  // Look through most attributes.
  if (auto attributed = dyn_cast<AttributedTypeRepr>(typeRepr)) {
    // Weak ownership implies optionality.
    if (attributed->getAttrs().getOwnership() == Ownership::Weak)
      return true;
    
    return isDefaultInitializable(attributed->getTypeRepr());
  }

  // Optional types are default-initializable.
  if (isa<OptionalTypeRepr>(typeRepr) ||
      isa<ImplicitlyUnwrappedOptionalTypeRepr>(typeRepr))
    return true;

  // Tuple types are default-initializable if all of their element
  // types are.
  if (auto tuple = dyn_cast<TupleTypeRepr>(typeRepr)) {
    // ... but not variadic ones.
    if (tuple->hasEllipsis())
      return false;

    for (auto elt : tuple->getElements()) {
      if (!isDefaultInitializable(elt.Type))
        return false;
    }

    return true;
  }

  // Not default initializable.
  return false;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. The existing logic is set up the way it is mostly because we don't have a way to get directly from a VarDecl to an initializer, but I guess it can stay that way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also care about the isNeverDefaultInitializable bit, so just looking at the TypeRepr isn't really sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes. How much of this do we care to expose, then? Should this just be exposed on PatternBindingDecl, or is it helpful anywhere else to expose the intermediate layers too (on TypeRepr and Pattern)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just using PatternBindingDecl is fine. We don't currently default-initialize anything but variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants