-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix SR-2134: emit 'rawValue' bodies for enums with a generic raw value #4038
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
Conversation
@@ -82,9 +82,16 @@ static void deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl) { | |||
|
|||
Type rawTy = enumDecl->getRawType(); | |||
assert(rawTy); | |||
// If the raw type is a generic parameter, unwrap it to match the type of the literals. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is correct. You should not depend on SubstitutedType for anything other than diagnostics...
@slavapestov Okay, I've improved the RawRepresentable derived conformance checking. I've tried it with badly-typed cases (generic and non-generic) and it doesn't crash any more. I haven't run the actual unit tests though |
@swift-ci test |
@@ -357,16 +353,15 @@ ValueDecl *DerivedConformance::deriveRawRepresentable(TypeChecker &tc, | |||
Decl *parentDecl, | |||
NominalTypeDecl *type, | |||
ValueDecl *requirement) { | |||
// Check preconditions. These should already have been diagnosed by | |||
// type-checking but we may still get here after recovery. | |||
// Check preconditions for RawRepresentable conformance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great to put the precondition check in its own function, since the same logic is duplicated in the two overloads of DerivedConformance::deriveRawRepresentable()
@karwa Thanks! So it looks like validateDecl() checks that all the types match for us -- can you just add a comment to that effect (and also, see my note about the duplicated checks). Other than that, this looks good, thanks! |
@karwa One more thing, can you also add tests for the invalid cases that you fixed? |
@swift-ci test os x |
@swift-ci test linux |
There is one test that still isn't working - decl/protocol/conforms/placement.swift In placement_2.swift:
In placement.swift:
The comments say something about 'suppressing' synthesized conformances, which I don't understand. What exactly is supposed to happen in this case? An error? |
Interesting. If I create a file I wonder why the test passes, then. In any case, the extension should be a no-op, or perhaps the members should be synthesized in the extension and not in the original type. |
I have come across so many tests which inexplicably haven't been failing before, I can't understand it. The mismatched enum types? That's already in test/Parse/enum.swift -- but try it in the interpreter, and it crashes. |
I don't see a test here that exercises that path. Maybe I'm missing something, but it could also be that the combination of a generic raw value and mismatched types crashes.
I think the difference between this test and yours is that you're giving the cases explicit raw values using '='. Try playing with that? |
Thanks for digging into this regardless -- figuring out these corner cases and adding detailed tests for them is really, really important for the long-term health of the language. |
Happy to contribute! I forgot how long enum.swift is; the test is on line 316:
|
case Powell(Int) // expected-error {{enum with raw type cannot have cases with arguments}} | ||
case Terwilliger(Int) = 17 // expected-error {{enum with raw type cannot have cases with arguments}} | ||
} | ||
|
||
enum RawTypeMismatch : Int { | ||
enum RawTypeMismatch : Int { // expected-error {{type 'RawTypeMismatch' does not conform to protocol 'RawRepresentable'}} | ||
case Barbur = "foo" // expected-error {{}} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it's this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be that in the interpreter, we go ahead and type check the bodies all synthesized members, but in the test, we do not.
Remember that bodies of synthesized members are built lazily, and we only crashed because we'd hit a case where a synthesized member subsequently had no body.
If you can think of a good way to re-organize the test, go ahead :) Don't kill yourself trying to understand some of the emergent behavior that comes up -- it's more important to make the existing tests pass, and add new tests for the problems you found. |
Do you mind squashing the commits into one patch, and referencing the bug number from the commit message too? |
Yes, I will, once I've fixed |
Okay, so it's been a day of learning :) The problem is that if the extension is in the primary file and the enum itself in some other file, we'll try to synthesize RawRep before the enum has been type-checked, before implicit cases have been assigned values, etc. The other way around (enum in primary file) works. I'm going to try and fix it by removing the suppression behaviour (even though it seems intentional). So declaring RawRepresentable will be a no-op, although of course you can provide your own implementation if you don't want the synthesised one. It doesn't really make sense that:
becomes:
Meanwhile,
becomes:
Feels like a hack for some standard-library thing. @gribozavr, any idea? |
After a little rethink, I settled on the idea that declaring a raw type (or giving a value to an element, which already requires that you explicitly specify a raw type in the inheritance clause) should count as an explicit declaration to synthesise RawRepresentable conformance. The inheritance clause and label statements in enums are really just syntactic sugar. It doesn't change anything about the underlying storage of the enum -- and even if it did, you wouldn't be able to change that in an extension. That would be like re-parenting a class. So that means you can't explicitly declare RawRepresentable on an enum which also has a raw type any more. Doing so would be a redundant declaration, which the compiler considers an error. |
!elt->getTypeCheckedRawValueExpr()->getType()->isEqual(rawTy)) { | ||
return; | ||
} | ||
assert(elt->getTypeCheckedRawValueExpr() && "Enum element has no literal - missing a call to checkEnumRawValues()"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines should be shorter than 80 characters
This latest version looks good. Please just fix the warnings and line length and we're good to go! |
@swift-ci Please smoke test |
if (!genericParams) { | ||
// Failed: dependent type in non-generic context. | ||
return Type(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is the right thing to do (how the function is intended to work), but the alternative is to duplicate these checks before attempting to map the type. There's an argument that this function shouldn't assert on failure conditions at all - in most other cases, the type-checker keeps going even after discovering invalid declarations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added these checks recently. There is a distinction between failure conditions that arise from invalid user input and failure conditions that arise from bugs inside the type checker. The latter should assert.
Note that if there are no generic parameters, we should not hit the assert, because the given type should not contain any type parameters. So if canType->hasTypeParameter() is true but genericParams is null, we have a bug.
Are you seeing this in one of the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
09650-swift-typebase-getcanonicaltype.swift
From the fuzzer, so it's not valid code anyway.
When we get the enum's raw type in canSynthesizeRawRep, it's a generic type param and isInvalid() is false, so I'm not sure how we should know not to try and map it to the context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem then is we have an enum with a raw type that's generic, but no generic parameter list. This doesn't make sense, because any generic types that appear in declarations must come from a generic parameter list from an outer scope.
Note that even if the user's code is invalid, we might build invalid declarations, but they should still be self-consistent.
This means some other part of the compiler upstream of you constructed an invalid enum declaration, or forgot to set the generic parameter list somehow. If you figure out what's going on and fix the underlying problem, you might be able to squash a few more compiler crashers.
Or, you can just XFAIL the compiler crasher for now. Having one or two regressions isn't the end of the world, especially since you improved matters in other respects.
cc8790d
to
c1fb3a6
Compare
@@ -259,6 +261,7 @@ static ConstructorDecl *deriveRawRepresentable_init(TypeChecker &tc, | |||
KnownProtocolKind::Equatable); | |||
assert(equatableProto); | |||
assert(tc.conformsToProtocol(rawType, equatableProto, enumDecl, None)); | |||
(void)equatableProto; // silence unused variable warning when assertions off. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is not necessary, this is a common idiom in our code. :)
@karwa Are you still working on this? Let me know if you need some help! |
@slavapestov yes! Been ill, will fix it up tonight |
No worries, get well soon! |
conformance Fixes SR-2134
@slavapestov Fixed, and while I was there I didn't like the look of the new errors (not very descriptive where the RawRepresentable conformance comes from), so I made them more descriptive. |
@swift-ci Please smoke test os x |
@@ -4718,6 +4730,19 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc, | |||
dc->getDeclaredTypeInContext(), | |||
diag.Protocol->getName()); | |||
|
|||
// Special case: explain that 'RawRepresentable' conformance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation
Thanks! |
@swift-ci Please smoke test macOS and merge |
@swift-ci Please smoke test macOS |
Alright @karwa, I think you can close that JIRA now :) |
…ynthesized` again. This reverts part of swiftlang#4038 which made the compiler consider it to be an `Explicit` conformance, breaking source code that was accepted in Swift 3.0 which declared a raw type as well as explicit conformance to `RawRepresentable` (reported as rdar://problem/30386658). While I'm here, a couple of spot fixes: - Ensure an enum's raw value exprs are type-checked before checking conformances of any of its extensions, since the RawRepresentable conformance derivation will blow up if the raw value exprs haven't been checked. Fixes an order dependency issue if `extension Foo: RawRepresentable {}` gets checked before `enum Foo: Int { ... }`. - Don't display the custom `enum_declares_rawrep_with_raw_type` diagnostic if the source location for the enum's inheritance clause is invalid, so that we don't emit a dislocated diagnostic.
…ynthesized` again. This reverts part of swiftlang#4038 which made the compiler consider it to be an `Explicit` conformance, breaking source code that was accepted in Swift 3.0 which declared a raw type as well as explicit conformance to `RawRepresentable` (reported as rdar://problem/30386658). While I'm here, a couple of spot fixes: - Ensure an enum's raw value exprs are type-checked before checking conformances of any of its extensions, since the RawRepresentable conformance derivation will blow up if the raw value exprs haven't been checked. Fixes an order dependency issue if `extension Foo: RawRepresentable {}` gets checked before `enum Foo: Int { ... }`. - Don't display the custom `enum_declares_rawrep_with_raw_type` diagnostic if the source location for the enum's inheritance clause is invalid, so that we don't emit a dislocated diagnostic.
What's in this pull request?
Resolved bug number: (SR-2134)
Before merging this pull request to apple/swift repository:
Triggering Swift CI
The swift-ci is triggered by writing a comment on this PR addressed to the GitHub user @swift-ci. Different tests will run depending on the specific comment that you use. The currently available comments are:
Smoke Testing
A smoke test on macOS does the following:
device standard libraries are not built.
version of these tests are not run.
A smoke test on Linux does the following:
tests are not run.
Validation Testing
Lint Testing
Note: Only members of the Apple organization can trigger swift-ci.