Skip to content

Implement SE-0068: Expanding Swift Self to class members and value types #3866

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,10 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {

/// If this DeclContext is a protocol extension, return the extended protocol.
ProtocolDecl *getAsProtocolExtensionContext() const;


/// Retrieve the nearest enclosing nominal type context.
NominalTypeDecl *getEnclosingNominalContext() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: I don't think it's correct to say a nominal is "enclosing" when you're in an extension.

Copy link
Contributor

Choose a reason for hiding this comment

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

getInnermostTypeContext()->getDeclaredTypeInContext()?

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 was a refactor of a global static function, but I will remove it and replace it with that, thanks


/// \brief Retrieve the generic parameter 'Self' from a protocol or
/// protocol extension.
///
Expand Down
15 changes: 5 additions & 10 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1428,7 +1428,7 @@ ERROR(requires_conformance_nonprotocol,none,
ERROR(requires_not_suitable_archetype,none,
"%select{|first |second }0type %1 in %select{conformance|same-type}2 "
"requirement does not refer to a generic parameter or associated type",
(int, TypeLoc, int))
(int, Type, int))
ERROR(requires_no_same_type_archetype,none,
"neither type in same-type refers to a generic parameter or "
"associated type",
Expand Down Expand Up @@ -1713,13 +1713,9 @@ ERROR(broken_equatable_eq_operator,none,
ERROR(no_equal_overload_for_int,none,
"no overload of '==' for Int", ())

// Dynamic Self
ERROR(dynamic_self_non_method,none,
// Self
ERROR(self_non_method,none,
"%select{global|local}0 function cannot return 'Self'", (bool))
ERROR(dynamic_self_struct_enum,none,
"%select{struct|enum}0 method cannot return 'Self'; "
"did you mean to use the %select{struct|enum}0 type %1?",
(int, Identifier))

// Duplicate declarations
ERROR(duplicate_enum_element,none,
Expand Down Expand Up @@ -2572,9 +2568,8 @@ ERROR(array_literal_intrinsics_not_found,none,
"Array<T>", ())
ERROR(bool_intrinsics_not_found,none,
"broken standard library: cannot find intrinsic operations on Bool", ())
ERROR(self_in_nominal,none,
"'Self' is only available in a protocol or as the result of a "
"method in a class; did you mean %0?", (Identifier))
ERROR(self_outside_nominal,none,
"'Self' is only available in a type", ())
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: "within" instead of "in"

ERROR(class_super_access,none,
"class %select{must be declared "
"%select{private|fileprivate|internal|PUBLIC}2"
Expand Down
2 changes: 1 addition & 1 deletion lib/AST/ArchetypeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1427,7 +1427,7 @@ bool ArchetypeBuilder::addRequirement(const RequirementRepr &Req) {
// FIXME: Poor location information.
// FIXME: Delay diagnostic until after type validation?
Diags.diagnose(Req.getColonLoc(), diag::requires_not_suitable_archetype,
0, Req.getSubjectLoc(), 0);
0, Req.getSubjectLoc().getType(), 0);
return true;
}

Expand Down
11 changes: 11 additions & 0 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ ProtocolDecl *DeclContext::getAsProtocolExtensionContext() const {
getAsGenericTypeOrGenericTypeExtensionContext());
}

NominalTypeDecl * DeclContext::getEnclosingNominalContext() const {
auto dc = this;
while (dc->isLocalContext())
dc = dc->getParent();

if (auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext())
return nominal;

return nullptr;
}

GenericTypeParamDecl *DeclContext::getProtocolSelf() const {
auto *proto = getAsProtocolOrProtocolExtensionContext();
assert(proto && "not a protocol");
Expand Down
12 changes: 12 additions & 0 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,18 @@ resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *DC) {
auto Lookup = lookupUnqualified(DC, Name, Loc, LookupOptions);

if (!Lookup) {
// Recover from not finding 'Self' in a nominal type, if we are
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 it makes sense to do this as recovery rather than unilaterally. Recovery means that a top-level value named "Self" will take precedence over the language feature.

Copy link
Contributor Author

@joewillsher joewillsher Jul 30, 2016

Choose a reason for hiding this comment

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

That precedence is used currently so the protocol's dynamic Self parameter is used over this. I do agree that this is kind of problematic though — where would you suggest putting the check?

// in a type context we can return the TypeExpr
if (Name.getBaseName() == Context.Id_Self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: this should also check that there are no argument labels (Name.isSimpleName()).

if (auto nominal = DC->getEnclosingNominalContext())
return TypeExpr::createForDecl(Loc, nominal, false);

// Warn if 'Self' is referenced outside a nominal type context
diagnose(Loc, diag::self_outside_nominal)
.highlight(UDRE->getSourceRange());
return new (Context) ErrorExpr(UDRE->getSourceRange());
}

// If we failed lookup of an operator, check to see it to see if it is
// because two operators are juxtaposed e.g. (x*-4) that needs whitespace.
// If so, emit specific diagnostics for it.
Expand Down
25 changes: 7 additions & 18 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4511,7 +4511,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {

// Dynamic 'Self' is only permitted on methods.
if (!dc->isTypeContext()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about local functions within types?

TC.diagnose(simpleRepr->getIdLoc(), diag::dynamic_self_non_method,
TC.diagnose(simpleRepr->getIdLoc(), diag::self_non_method,
dc->isLocalContext());
simpleRepr->setInvalid();
return true;
Expand All @@ -4522,26 +4522,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
auto declaredType = dc->getDeclaredTypeOfContext();
if (declaredType->is<ErrorType>())
return false;

auto nominal = declaredType->getAnyNominal();
if (!isa<ClassDecl>(nominal) && !isa<ProtocolDecl>(nominal)) {
int which;
if (isa<StructDecl>(nominal))
which = 0;
else if (isa<EnumDecl>(nominal))
which = 1;
else
llvm_unreachable("Unknown nominal type");
TC.diagnose(simpleRepr->getIdLoc(), diag::dynamic_self_struct_enum,
which, nominal->getName())
.fixItReplace(simpleRepr->getIdLoc(), nominal->getName().str());
simpleRepr->setInvalid();
return true;

// 'Self' return types in classes or protocols are dynamic 'Self'
if (isa<ClassDecl>(nominal) || isa<ProtocolDecl>(nominal)) {
// Note that the function has a dynamic Self return type and set
// the return type component to the dynamic self type.
func->setDynamicSelf(true);
}

// Note that the function has a dynamic Self return type and set
// the return type component to the dynamic self type.
func->setDynamicSelf(true);
return false;
}

Expand Down
69 changes: 27 additions & 42 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -698,17 +698,6 @@ static Type resolveTypeDecl(TypeChecker &TC, TypeDecl *typeDecl, SourceLoc loc,
return type;
}

/// Retrieve the nearest enclosing nominal type context.
static NominalTypeDecl *getEnclosingNominalContext(DeclContext *dc) {
while (dc->isLocalContext())
dc = dc->getParent();

if (auto nominal = dc->getAsNominalTypeOrNominalTypeExtensionContext())
return nominal;

return nullptr;
}

/// Diagnose a reference to an unknown type.
///
/// This routine diagnoses a reference to an unknown type, and
Expand All @@ -728,28 +717,6 @@ static Type diagnoseUnknownType(TypeChecker &tc, DeclContext *dc,
UnsatisfiedDependency *unsatisfiedDependency) {
// Unqualified lookup case.
if (parentType.isNull()) {
// Attempt to refer to 'Self' within a non-protocol nominal
// type. Fix this by replacing 'Self' with the nominal type name.
NominalTypeDecl *nominal = nullptr;
if (comp->getIdentifier() == tc.Context.Id_Self &&
!isa<GenericIdentTypeRepr>(comp) &&
(nominal = getEnclosingNominalContext(dc))) {
// Retrieve the nominal type and resolve it within this context.
assert(!isa<ProtocolDecl>(nominal) && "Cannot be a protocol");
auto type = resolveTypeDecl(tc, nominal, comp->getIdLoc(), dc, nullptr,
options, resolver, unsatisfiedDependency);
if (type->is<ErrorType>())
return type;

// Produce a Fix-It replacing 'Self' with the nominal type name.
tc.diagnose(comp->getIdLoc(), diag::self_in_nominal, nominal->getName())
.fixItReplace(comp->getIdLoc(), nominal->getName().str());
comp->overwriteIdentifier(nominal->getName());
comp->setValue(nominal);
return type;
}

// Fallback.
SourceLoc L = comp->getIdLoc();
SourceRange R = SourceRange(comp->getIdLoc());

Expand Down Expand Up @@ -826,19 +793,37 @@ resolveTopLevelIdentTypeComponent(TypeChecker &TC, DeclContext *DC,
resolver, unsatisfiedDependency);
}

// Identifier is 'Self'
if (comp->getIdentifier() == TC.Context.Id_Self) {
// Dynamic 'Self' in the result type of a function body.
if (options.contains(TR_DynamicSelfResult)) {
auto func = cast<FuncDecl>(DC);
assert(func->hasDynamicSelf() && "Not marked as having dynamic Self?");

return func->getDynamicSelf();
}

// Otherwise it is a concrete reference to the enclosing
// nominal type if we are in a type context.
if (auto nominal = DC->getEnclosingNominalContext()) {
// In protocols, 'Self' is always the 'Self' generic param,
// so don't resolve it here.
if (!nominal->getAsProtocolOrProtocolExtensionContext())
return resolveTypeDecl(TC, nominal, comp->getIdLoc(), DC, nullptr,
options, resolver, unsatisfiedDependency);
} else {
// Warn if 'Self' is referenced outside a nominal type context.
TC.diagnose(comp->getIdLoc(), diag::self_outside_nominal)
.highlight(comp->getSourceRange());
comp->setInvalid();
return ErrorType::get(TC.Context);
}
}

// Resolve the first component, which is the only one that requires
// unqualified name lookup.
DeclContext *lookupDC = DC;

// Dynamic 'Self' in the result type of a function body.
if (options.contains(TR_DynamicSelfResult) &&
comp->getIdentifier() == TC.Context.Id_Self) {
auto func = cast<FuncDecl>(DC);
assert(func->hasDynamicSelf() && "Not marked as having dynamic Self?");

return func->getDynamicSelf();
}

// For lookups within the generic signature, look at the generic
// parameters (only), then move up to the enclosing context.
if (options.contains(TR_GenericSignature)) {
Expand Down
4 changes: 1 addition & 3 deletions test/decl/ext/generic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ extension Array where Element == String { } // expected-error{{same-type require

extension GenericClass : P3 where T : P3 { } // expected-error{{extension of type 'GenericClass' with constraints cannot have an inheritance clause}}

extension GenericClass where Self : P3 { }
// expected-error@-1{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'GenericClass'?}} {{30-34=GenericClass}}
// expected-error@-2{{type 'GenericClass' in conformance requirement does not refer to a generic parameter or associated type}}
extension GenericClass where Self : P3 { } // expected-error{{type 'GenericClass<T>' in conformance requirement does not refer to a generic parameter or associated type}}

protocol P4 {
associatedtype T
Expand Down
23 changes: 10 additions & 13 deletions test/decl/func/dynamic_self.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@ func inFunction() {
}

struct S0 {
func f() -> Self { } // expected-error{{struct method cannot return 'Self'; did you mean to use the struct type 'S0'?}}{{15-19=S0}}
func f() -> Self { } // okay

func g(_ ds: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'S0'?}}{{16-20=S0}}
func g(_ ds: Self) { } // okay
}

enum E0 {
func f() -> Self { } // expected-error{{enum method cannot return 'Self'; did you mean to use the enum type 'E0'?}}{{15-19=E0}}
func f() -> Self { } // okay

func g(_ ds: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'E0'?}}{{16-20=E0}}
func g(_ ds: Self) { } // okay
}

class C0 {
func f() -> Self { } // okay

func g(_ ds: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'C0'?}}{{16-20=C0}}

func h(_ ds: Self) -> Self { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'C0'?}}{{16-20=C0}}
func g(_ ds: Self) { } // okay
func h(_ ds: Self) -> Self { } // okay
}

protocol P0 {
Expand Down Expand Up @@ -73,7 +71,7 @@ class C1 {
if !b { return self.dynamicType.init(int: 5) }

// Can't utter Self within the body of a method.
var _: Self = self // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'C1'?}} {{12-16=C1}}
var _: Self = self

// Okay to return 'self', because it has the appropriate type.
return self // okay
Expand All @@ -82,14 +80,13 @@ class C1 {
// Type methods have a self of type Self.Type.
class func factory(_ b: Bool) -> Self {
// Check directly.
var x: Int = self // expected-error{{cannot convert value of type 'Self.Type' to specified type 'Int'}}
var _: Int = self // expected-error{{cannot convert value of type 'Self.Type' to specified type 'Int'}}

// Can't utter Self within the body of a method.
var c1 = C1(int: 5) as Self // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'C1'?}} {{28-32=C1}}
_ = C1(int: 5) as Self

if b { return self.init(int: 5) }

return Self() // expected-error{{use of unresolved identifier 'Self'}} expected-note {{did you mean 'self'?}}
return Self() // expected-error{{missing argument for parameter 'int' in call}}
}
}

Expand Down
69 changes: 60 additions & 9 deletions test/type/self.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
// RUN: %target-parse-verify-swift

struct S0<T> {
func foo(_ other: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'S0'?}}{{21-25=S0}}
struct S0 {
func foo0() -> Self { return self }
func foo1(_ a: Self) { }
}

class C0<T> {
func foo(_ other: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'C0'?}}{{21-25=C0}}
class C0 {
func foo0() -> Self { return self }
func foo1(_ a: Self) { }
}

enum E0<T> {
func foo(_ other: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'E0'?}}{{21-25=E0}}
enum E0 {
func foo0() -> Self { return self }
func foo1(_ a: Self) { }
}
// Test generics
struct S1<T> {
func foo0(_ other: Self) {
_ = Self.self // check lookup of 'Self' metatype
}
func foo1() -> Self { return self }
}
class C1<T> {
func foo0(_ other: Self) { }
func foo1() -> Self { return self }
}
enum E1<T> {
func foo0(_ other: Self) { }
func foo1() -> Self { return self }
}

// rdar://problem/21745221
Expand All @@ -19,9 +35,44 @@ struct X {

extension X {
struct Inner {
func foo0() -> Self { return self }
}
}

extension X.Inner {
func foo(_ other: Self) { } // expected-error{{'Self' is only available in a protocol or as the result of a method in a class; did you mean 'Inner'?}}{{21-25=Inner}}
func foo1(_ other: Self) { }
}

// Test we are still using dynamic, not concrete, 'Self'
// for class and protocol methods which return 'Self'

class NonFinal {
func reference() -> Self {
return self
}
}
final class Final : NonFinal {
}
protocol Proto {
func copy() -> Self
}
struct Conf : Proto {
func copy() -> Self {
return self
}
}

func testClass() {
let f0 = Final()
let _: Final = f0.reference()
}
func testProto() {
let p0 = Conf()
let _: Conf = p0.copy()
}

let _: Self = () // expected-error{{'Self' is only available in a type}}
func noParent() -> Self { fatalError() } // expected-error{{global function cannot return 'Self'}}

_ = Self.self // expected-error {{'Self' is only available in a type}}