Skip to content

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

Merged
merged 2 commits into from
Aug 24, 2016

Conversation

karwa
Copy link
Contributor

@karwa karwa commented Aug 5, 2016

What's in this pull request?

Resolved bug number: (SR-2134)


Before merging this pull request to apple/swift repository:

  • Test pull request on Swift continuous integration.

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

Platform Comment
All supported platforms @swift-ci Please smoke test
All supported platforms @swift-ci Please smoke test and merge
OS X platform @swift-ci Please smoke test OS X platform
Linux platform @swift-ci Please smoke test Linux platform

A smoke test on macOS does the following:

  1. Builds the compiler incrementally.
  2. Builds the standard library only for macOS. Simulator standard libraries and
    device standard libraries are not built.
  3. lldb is not built.
  4. The test and validation-test targets are run only for macOS. The optimized
    version of these tests are not run.

A smoke test on Linux does the following:

  1. Builds the compiler incrementally.
  2. Builds the standard library incrementally.
  3. lldb is built incrementally.
  4. The swift test and validation-test targets are run. The optimized version of these
    tests are not run.
  5. lldb is tested.

Validation Testing

Platform Comment
All supported platforms @swift-ci Please test
All supported platforms @swift-ci Please test and merge
OS X platform @swift-ci Please test OS X platform
OS X platform @swift-ci Please benchmark
Linux platform @swift-ci Please test Linux platform

Lint Testing

Language Comment
Python @swift-ci Please Python lint

Note: Only members of the Apple organization can trigger swift-ci.

@@ -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.
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 not sure this is correct. You should not depend on SubstitutedType for anything other than diagnostics...

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

@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

@tkremenek
Copy link
Member

@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.
Copy link
Contributor

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()

@slavapestov slavapestov self-assigned this Aug 5, 2016
@slavapestov
Copy link
Contributor

@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!

@slavapestov
Copy link
Contributor

@karwa One more thing, can you also add tests for the invalid cases that you fixed?

@tkremenek
Copy link
Member

@swift-ci test os x

@tkremenek
Copy link
Member

@swift-ci test linux

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

There is one test that still isn't working - decl/protocol/conforms/placement.swift

In placement_2.swift:

enum MFSynthesizedEnum1 : Int { case a }

In placement.swift:

extension MFSynthesizedEnum1 : RawRepresentable { }

The comments say something about 'suppressing' synthesized conformances, which I don't understand. What exactly is supposed to happen in this case? An error?

@slavapestov
Copy link
Contributor

Interesting. If I create a file a.swift containing enum MFSynthesizedEnum1 : Int { case a }, and a file main.swift containing extension MFSynthesizedEnum1 : RawRepresentable { }, then swiftc a.swift main.swift crashes.

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.

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

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.
Generic raw values? That's already in test/Generics/inheritance.swift -- but it wasn't working, hence the bug report SR-2134

@slavapestov
Copy link
Contributor

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.

Generic raw values? That's already in test/Generics/inheritance.swift -- but it wasn't working, hence the bug report SR-2134

I think the difference between this test and yours is that you're giving the cases explicit raw values using '='. Try playing with that?

@slavapestov
Copy link
Contributor

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.

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

Happy to contribute!

I forgot how long enum.swift is; the test is on line 316:

enum RawTypeMismatch : Int {
  case Barbur = "foo" // expected-error {{}}
}

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 {{}}
}

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@slavapestov
Copy link
Contributor

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.

@slavapestov
Copy link
Contributor

Do you mind squashing the commits into one patch, and referencing the bug number from the commit message too?

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

Yes, I will, once I've fixed decl/protocol/conforms/placement.swift

@karwa
Copy link
Contributor Author

karwa commented Aug 5, 2016

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:

enum Foo : Int { case a }

becomes:

enum Foo : Int, RawRepresentable {
    case a
    Typealias RawValue = Int
    init?(rawValue: Int)
    var rawValue: Int { get }
    var hashValue: Int { get }
}

Meanwhile,

enum Foo : Int { case a = 10 }
extension Foo : RawRepresentable {}

becomes:

enum Foo : Int {
    case a
    var hashValue: Int { get }
}

Feels like a hack for some standard-library thing. @gribozavr, any idea?

@karwa
Copy link
Contributor Author

karwa commented Aug 7, 2016

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()");
Copy link
Contributor

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

@slavapestov
Copy link
Contributor

This latest version looks good. Please just fix the warnings and line length and we're good to go!

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test

if (!genericParams) {
// Failed: dependent type in non-generic context.
return Type();
}
Copy link
Contributor Author

@karwa karwa Aug 9, 2016

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.

Copy link
Contributor

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?

Copy link
Contributor Author

@karwa karwa Aug 9, 2016

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.

Copy link
Contributor

@slavapestov slavapestov Aug 9, 2016

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.

@karwa karwa force-pushed the generic-enums branch 2 times, most recently from cc8790d to c1fb3a6 Compare August 9, 2016 18:33
@@ -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.
Copy link
Contributor

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. :)

@slavapestov
Copy link
Contributor

@karwa Are you still working on this? Let me know if you need some help!

@karwa
Copy link
Contributor Author

karwa commented Aug 18, 2016

@slavapestov yes! Been ill, will fix it up tonight

@slavapestov
Copy link
Contributor

No worries, get well soon!

@karwa
Copy link
Contributor Author

karwa commented Aug 22, 2016

@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.

@slavapestov
Copy link
Contributor

@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
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation

@slavapestov
Copy link
Contributor

Thanks!

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test macOS and merge

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test macOS

@slavapestov slavapestov merged commit 1705caa into swiftlang:master Aug 24, 2016
@slavapestov
Copy link
Contributor

Alright @karwa, I think you can close that JIRA now :)

jckarter added a commit to jckarter/swift that referenced this pull request Feb 8, 2017
…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.
jckarter added a commit to jckarter/swift that referenced this pull request Feb 9, 2017
…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.
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.

4 participants