Skip to content
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

Attribute @_implements & deriving enum equality not-named == #8735

Merged

Conversation

graydon
Copy link
Contributor

@graydon graydon commented Apr 13, 2017

This implements (non-user-facing) attribute @_implements(Protocol, DeclName) that can be used to conform to a protocol requirement using a decl with a different name than the requirement.

This might sound weird, but it has (at least?) two uses we've already hit:

  1. Conforming to two protocols that use the same name for different operations (this arises when comparing floating point values)
  2. Conforming to a protocol that has a requirement that's an operator-name, without adding a new operator to the global operator list (this happens via implicit Equatable conformances on enums, and causes a lot of needless top-level inter-file dependencies)

After initial post and review, I've ticked off the following todos:

  • Limit lookup to only happen when locating witnesses
  • Limit lookup to the Context (protocol) name specified; currently it ignores that part of the name
  • Get it working for Operator names (including protocol members)
  • Add ~~~lots~~~ some more tests and fix all bugs those reveal
  • Check that @_implements only mentions protocols the decl conforms to

rdar://31607695

This was originally motivated by 30959593 (operators defeat incrementality), and I've now tacked on to the end a change that @_implements the derived enum == operator in terms of a different function (creatively named __derived_enum_equals)

@@ -1067,6 +1068,13 @@ void MemberLookupTable::addMember(Decl *member) {
// Add this declaration to the lookup set under its compound name and simple
// name.
vd->getFullName().addToLookupTable(Lookup, vd);

// Add this declaration to the lookup set under any @_implements name as well
Copy link
Contributor

Choose a reason for hiding this comment

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

How do you prevent name lookup on concrete types from finding the decl? We only want to see the implements name while doing conformance checks no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct! Forgot to put this on the todo list. Thanks!

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 the right place to hook this up is not inside name lookup, but in lib/Sema/TypeCheckProtocol.cpp, in particular lookupValueWitnesses(). When you begin conformance checking, maybe walk the members to collect all _implements attributes, building a mapping from requirement to witness inside the conformance. Then lookupValueWitness() can avoid most of the work by checking the pre-populated map first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion! Fixed.

// We parse a ParsedDeclName here just to make sure it's well formed early; we
// store it in the attr as a StringRef and re-parse on use, so as not to leak
// the ParsedDeclName type into Attr.h
ParsedDeclName parsedName = parseDeclName(ImplName.getValue());
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps the semantic checking can be done in lib/Sema/TypeCheckAttr.cpp, so you're not simply throwing away the result.

@slavapestov
Copy link
Contributor

I would also suggest adding some semantic checks to Sema that _implements only mentions requirements of protocols that the type conforms to.

Also do we want to allow _implements inside extensions? This might open up some interesting corner cases.

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from b2cb231 to cd53e8b Compare April 14, 2017 03:19
@graydon
Copy link
Contributor Author

graydon commented Apr 14, 2017

Pushed an update that reworks parsing and definition to handle operator names, permits the bare-minimum feature of @implements(Equatable, ==(:_:)). More comments welcome!

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from cd53e8b to 51561f0 Compare April 14, 2017 20:01
@graydon
Copy link
Contributor Author

graydon commented Apr 14, 2017

Pushed update to limit the name visibility to witness lookup.

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from 51561f0 to f06277d Compare April 16, 2017 02:53
@graydon graydon changed the title WIP / Do Not Merge Yet: Attribute @_implements Attribute @_implements Apr 16, 2017
@graydon
Copy link
Contributor Author

graydon commented Apr 16, 2017

Pushed variant that does everything on the todo list, with some new tests. Going to look at synthesizing this for the motivating case now (dependency-edge cutting) but please let me know if there's anything else you want to see for an initial merge.

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from f06277d to bcd1913 Compare April 17, 2017 01:18
@graydon graydon changed the title Attribute @_implements Attribute @_implements & deriving enum equality not-named == Apr 17, 2017
@graydon
Copy link
Contributor Author

graydon commented Apr 17, 2017

Pushed change that derives func== on enums using @_implements, cutting the provides-top-level dependency edge.

@@ -196,7 +196,8 @@ deriveEquatable_enum_eq(TypeChecker &tc, Decl *parentDecl, EnumDecl *enumDecl) {
// case A, B, C
//
// @derived
// func ==(a: SomeEnum<T...>, b: SomeEnum<T...>) -> Bool {
// @_implements(Equatable, ==(_:_:))
// func _DerivedEnumEquals(a: SomeEnum<T...>, b: SomeEnum<T...>) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this going to look ugly in generated interface printing? Maybe we could special case that to print this as == (or just skip synthesized decls altogether).

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 it's already marked implicit, so most people won't see it. There are just a few tests that try to print implicit declarations 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.

Yeah, it's implicit, no printing by default.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Drive-by thought:

Wouldn't it be neat to have this be consistent with the "__Anonymous_field" and "__Unnamed_union" terminology introduced in #6935? (I guess it'd be something like "__Derived_conformance_enum_equals".)

Copy link
Contributor

Choose a reason for hiding this comment

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

…on that note, it's not a type, so lowercase would make more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to __derived_enum_equals after checking with Jordan.

ProtoTypeLoc.setType(T);

// Definite error-types were already diagnosed in resolveType.
if (!T || T->is<ErrorType>())
Copy link
Contributor

Choose a reason for hiding this comment

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

or hasErrorType(), even

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Assuming you mean hasError(), and to replace the second disjunct with that test, then: fixed)


// Do an initial check to see if there are any @_implements remappings
// for this requirement.
if (req->isProtocolRequirement() && req->hasName()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe factor this out into a new lookupValueWitnessViaImplementsAttr() and use early return to avoid the "} cascade of death"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -16,6 +16,7 @@
//===----------------------------------------------------------------------===//
#include "swift/Sema/TypeCheckRequest.h"
#include "swift/AST/Decl.h"
#include "swift/AST/TypeRepr.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. the call to std::get<0>(getTypeResolutionPayload())->getLoc() needs it.

@slavapestov
Copy link
Contributor

This looks great, nice job!

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from bcd1913 to 45b5fb1 Compare April 17, 2017 23:38
@graydon
Copy link
Contributor Author

graydon commented Apr 17, 2017

Pushed variant addressing recent review comments from Slava.

@graydon
Copy link
Contributor Author

graydon commented Apr 17, 2017

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - b2cb2313e90495a174831abdc2b0567c6023f2dd
Test requested by - @graydon

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - b2cb2313e90495a174831abdc2b0567c6023f2dd
Test requested by - @graydon

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from 45b5fb1 to d65027f Compare April 18, 2017 18:13
@graydon
Copy link
Contributor Author

graydon commented Apr 18, 2017

@swift-ci please smoke test

@@ -267,6 +267,11 @@ SIMPLE_DECL_ATTR(discardableResult, DiscardableResult,

SIMPLE_DECL_ATTR(GKInspectable, GKInspectable, OnVar, 66)

DECL_ATTR(_implements, Implements,
OnFunc | OnVar | OnSubscript | OnTypeAlias
| NotSerialized | UserInaccessible,
Copy link
Member

Choose a reason for hiding this comment

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

If the intent is for these declarations to be invisible to normal name lookup (is that the intent?) or hide it from code completion, then you'll probably need to serialize the attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

The attribute isn't what triggers either of those things, so we're okay.

@@ -90,6 +90,7 @@ IDENTIFIER_WITH_NAME(NativeClassLayout, "_NativeClass")
// Operators
IDENTIFIER_WITH_NAME(MatchOperator, "~=")
IDENTIFIER_WITH_NAME(EqualsOperator, "==")
IDENTIFIER_WITH_NAME(DerivedEnumEquals, "_DerivedEnumEquals")
Copy link
Member

Choose a reason for hiding this comment

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

Lowercase _derivedEnumEquals because it's a function (not a type)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed to __derived_enum_equals per conversation w/ Jordan

@@ -196,7 +196,9 @@ deriveEquatable_enum_eq(TypeChecker &tc, Decl *parentDecl, EnumDecl *enumDecl) {
// case A, B, C
//
// @derived
// func ==(a: SomeEnum<T...>, b: SomeEnum<T...>) -> Bool {
// @_implements(Equatable, ==(_:_:))
// func __derived_conformance_enum_equals(a: SomeEnum<T...>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/__derived_conformance/__derived/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@graydon graydon force-pushed the rdar-30959593-operators-defeat-incrementality branch from d65027f to e6027ee Compare April 19, 2017 06:54
@graydon
Copy link
Contributor Author

graydon commented Apr 19, 2017

Pushed variant with fixed name and change to user-availability bit such that code completion never tells the user about __derived_enum_equals.

@graydon
Copy link
Contributor Author

graydon commented Apr 19, 2017

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - 45b5fb11f9c2c72f88283cceb3aafabc56915b5f
Test requested by - @graydon

@swift-ci
Copy link
Contributor

Build failed
Jenkins build - Swift Test Linux Platform
Git Commit - 45b5fb11f9c2c72f88283cceb3aafabc56915b5f
Test requested by - @graydon

@graydon
Copy link
Contributor Author

graydon commented Apr 19, 2017

@swift-ci please test

@graydon
Copy link
Contributor Author

graydon commented Apr 19, 2017

r=dgregor, merging.

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