Skip to content

Conversation

drexin
Copy link
Contributor

@drexin drexin commented Nov 21, 2020

This pull request adds Codable synthesis for enums with associated values.

@drexin
Copy link
Contributor Author

drexin commented Nov 21, 2020

There are still some things to clean up and tests to add here.

@DougGregor
Copy link
Member

@swift-ci build toolchain

@swift-ci
Copy link
Contributor

swift-ci commented Dec 1, 2020

Linux Toolchain (Ubuntu 16.04)
Download Toolchain
Git Sha - a34d00e81b8c29c82ba97a1e2db7b0054dfc8265

Install command
tar zxf swift-PR-34855-510-ubuntu16.04.tar.gz
More info

@swift-ci
Copy link
Contributor

swift-ci commented Dec 1, 2020

macOS Toolchain
Download Toolchain
Git Sha - a34d00e81b8c29c82ba97a1e2db7b0054dfc8265

Install command
tar -zxf swift-PR-34855-798-osx.tar.gz --directory ~/

@drexin drexin force-pushed the wip-enum-codable branch 2 times, most recently from 6814196 to 20bc352 Compare January 21, 2021 23:44
@drexin
Copy link
Contributor Author

drexin commented Jan 22, 2021

@swift-ci test

@drexin
Copy link
Contributor Author

drexin commented Jan 22, 2021

@swift-ci build toolchain

@swift-ci
Copy link
Contributor

Linux Toolchain (Ubuntu 16.04)
Download Toolchain
Git Sha - 20bc352

Install command
tar zxf swift-PR-34855-521-ubuntu16.04.tar.gz
More info

@drexin
Copy link
Contributor Author

drexin commented Feb 18, 2021

@swift-ci smoke test

2 similar comments
@drexin
Copy link
Contributor Author

drexin commented Feb 19, 2021

@swift-ci smoke test

@drexin
Copy link
Contributor Author

drexin commented Feb 19, 2021

@swift-ci smoke test

Copy link
Contributor

@beccadax beccadax left a comment

Choose a reason for hiding this comment

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

I don't feel like I've caught everything in this PR that could be improved, but here's a starting point.

If you want to go the extra mile, I think there are pretty strong structural similarities between enums (and their elements), associated values (and their parameters), and structs (and their stored properties) which you could exploit to make this code more principled and easier to follow. But you can always leave that refactoring for someone else.

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems kinda bad, but I guess that's why we have a feature flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is copy pasted from the struct case and not specific to the additions in this PR. It just felt right to have the comment here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's behind a feature flag, because the proposal has not yet been accepted.

Copy link
Contributor

Choose a reason for hiding this comment

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

The original context is bogus as well. This deriving mechanism actively suppresses downstream error diagnostics in a bizarre attempt to restructure the diagnostics it emits. This code needs refactoring, but this PR is not the place to do it.

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 now that TypeChecker is just a namespace and we have more requests that we can perform real validation here.

@drexin
Copy link
Contributor Author

drexin commented Feb 19, 2021

@swift-ci smoke test

@drexin drexin requested a review from beccadax February 19, 2021 09:15
@drexin
Copy link
Contributor Author

drexin commented Feb 23, 2021

@swift-ci smoke test

@drexin
Copy link
Contributor Author

drexin commented Feb 23, 2021

@beccadax When you have a minute, could you please give this another read?

@drexin
Copy link
Contributor Author

drexin commented Feb 24, 2021

@swift-ci smoke test

@drexin
Copy link
Contributor Author

drexin commented Feb 24, 2021

@swift-ci smoke test linux

Copy link
Contributor

@ktoso ktoso left a comment

Choose a reason for hiding this comment

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

The synthesis is looking good, as far as I had to work with similar things recently at least :-)
Very excited to see this polished up!

Copy link
Contributor

@beccadax beccadax left a comment

Choose a reason for hiding this comment

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

Sorry about the delay. I think there's a bunch more you could do to clean this up, but you can do that in a followup PR if you need to get this in.

Copy link
Contributor

Choose a reason for hiding this comment

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

Id_CodingKeys is known to start with an uppercase letter, so you can just do a normal scratch += here.

auto *codingKeyCase =
lookupEnumCase(C, codingKeysEnum, elementDecl->getBaseIdentifier());
if (!codingKeyCase)
return nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

You should do this early return before you allocate inherited, and probably as early as possible to avoid unnecessary work.


// if the type conforms to {En,De}codable, add it to the enum.
Identifier paramIdentifier = getVarNameForCoding(paramDecl);
bool generatedName = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable doesn't seem to be used anywhere.

return dyn_cast<NominalTypeDecl>(decl);
}

static EnumDecl *addImplicitCodingKeys_enum(EnumDecl *target) {
Copy link
Contributor

Choose a reason for hiding this comment

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

With a couple tweaks to the whitespace, it's clear that this function "rhymes" quite a bit with addImplicitCaseCodingKeys() below:

Screen Shot 2021-02-26 at 2 16 06 PM

Do you think you might be able to merge them or extract some common helpers?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking further down, I also see some commonalities with existing code in addImplicitCodingKeys().

return false;
}

auto *codingKeysTypeDecl = _codingKeysTypeDecl;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of this copy? (It looks like maybe something modifies codingKeysTypeDecl? If so, you might want to explain that.)

Comment on lines +1657 to +1705
for (auto entry : llvm::enumerate(*elt->getParameterList())) {
auto *paramDecl = entry.value();
Identifier identifier = getVarNameForCoding(paramDecl);
if (identifier.empty()) {
identifier = C.getIdentifier("_" + std::to_string(entry.index()));
}
auto *caseCodingKey = lookupEnumCase(C, caseCodingKeys, identifier);

params.push_back(getVarNameForCoding(paramDecl));

// If no key is defined for this parameter, use the default value
if (!caseCodingKey) {
// This should have been verified to have a default expr in the
// CodingKey synthesis
assert(paramDecl->hasDefaultExpr());
decodeCalls.push_back(paramDecl->getTypeCheckedDefaultExpr());
continue;
}

// Type.self
auto *parameterTypeExpr = TypeExpr::createImplicit(
funcDC->mapTypeIntoContext(paramDecl->getInterfaceType()), C);
auto *parameterMetaTypeExpr =
new (C) DotSelfExpr(parameterTypeExpr, SourceLoc(), SourceLoc());
// BarCodingKeys.x
auto *metaTyRef =
TypeExpr::createImplicit(caseCodingKeys->getDeclaredType(), C);
auto *keyExpr =
new (C) MemberRefExpr(metaTyRef, SourceLoc(), caseCodingKey,
DeclNameLoc(), /*Implicit=*/true);

auto *nestedContainerExpr = new (C)
DeclRefExpr(ConcreteDeclRef(nestedContainerDecl), DeclNameLoc(),
/*Implicit=*/true, AccessSemantics::DirectToStorage);
// decode(_:, forKey:)
auto *decodeCall = UnresolvedDotExpr::createImplicit(
C, nestedContainerExpr, C.Id_decode, {Identifier(), C.Id_forKey});

// nestedContainer.decode(Type.self, forKey: BarCodingKeys.x)
auto *callExpr = CallExpr::createImplicit(
C, decodeCall, {parameterMetaTypeExpr, keyExpr},
{Identifier(), C.Id_forKey});

// try nestedContainer.decode(Type.self, forKey: BarCodingKeys.x)
auto *tryExpr = new (C) TryExpr(SourceLoc(), callExpr, Type(),
/*Implicit=*/true);

decodeCalls.push_back(tryExpr);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't diff, but I assume that once again this loop body is very similar to deriveBodyDecodable_init()'s.

Comment on lines +1059 to +1063
llvm::SmallString<128> buffer;
buffer.append("Case '");
buffer.append(elt->getBaseIdentifier().str());
buffer.append(
"' cannot be decoded because it is not defined in CodingKeys.");
Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to look at llvm::Twine—it's really good at building strings like this.

Comment on lines +1761 to +1765
auto *debugMessage = new (C) StringLiteralExpr(
StringRef("Invalid number of keys found, expected one."), SourceRange(),
/* Implicit */ true);
auto *throwStmt = createThrowDecodingErrorTypeMismatchStmt(
C, funcDC, targetEnum, containerExpr, debugMessage);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm noticing that you have two helper functions to create throw statements, but you use each of them once and in both cases you need to separately build a StringLiteralExpr first. You might want to see if you can merge them together and reduce duplication by tweaking the parameters slightly.

Comment on lines +1148 to +1173
// generate: case .<Case>:
auto pat = new (C) EnumElementPattern(
TypeExpr::createImplicit(enumDecl->getDeclaredType(), C), SourceLoc(),
DeclNameLoc(), DeclNameRef(), elt, subpattern);
pat->setImplicit();

auto labelItem = CaseLabelItem(pat);
auto body = BraceStmt::create(C, SourceLoc(), caseStatements, SourceLoc());
cases.push_back(CaseStmt::create(C, CaseParentKind::Switch, SourceLoc(),
labelItem, SourceLoc(), SourceLoc(), body,
/*case body vardecls*/ caseBodyVarDecls));
}

// generate: switch self { }
auto enumRef =
new (C) DeclRefExpr(ConcreteDeclRef(selfRef), DeclNameLoc(),
/*implicit*/ true, AccessSemantics::Ordinary);

auto switchStmt = SwitchStmt::create(LabeledStmtInfo(), SourceLoc(), enumRef,
SourceLoc(), cases, SourceLoc(), C);
statements.push_back(switchStmt);

auto *body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc(),
/*implicit=*/true);
return {body, /*isTypeChecked=*/false};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You know, this part, and probably a good chunk of the preamble, is fairly similar to the switch statement generation in deriveBodyDecodable_enum_init(). I wonder if you could extract a helper that takes a lambda to call with each case from the enum it's switching over?

Comment on lines +2911 to +2917
NOTE(codable_enum_duplicate_case_name_here,none,
"cannot automatically synthesize %0 because %1 has duplicate "
"case name %2", (Type, Type, Identifier))
NOTE(codable_enum_duplicate_parameter_name_here,none,
"cannot automatically synthesize %0 for %1 because "
"user defined parameter name %2 in %3 conflicts with "
"automatically generated parameter name", (Type, Type, Identifier, Identifier))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: We usually put the argument types on a separate line, partially to avoid line limit violations.

@drexin
Copy link
Contributor Author

drexin commented Feb 27, 2021

@beccadax Thanks for the feedback. Follow up PR sounds great. I‘d love to get this in as soon as possible.

@drexin drexin merged commit 8e0a260 into swiftlang:main Feb 27, 2021
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.

6 participants