Syntax sugar for declaring nested types

I have been using empty enums as "namespaces" and have been pretty pleased with how that has played out. Unfortunately, it leads to undesirable indentation as a level of indentation is required to declare a nested type:

enum Foo {}

// elsewhere:
extension Foo {
    struct Bar {
    }
}

This level of indentation is not required when extending the nested type:

extension Foo.Bar {
}

I have considered experimenting with this layout to eliminate the indentation:

extension Foo { struct Bar {
}}

But I haven't actually done this because it is clunky, somewhat hard to read, and I work on a large team where it may have unintended consequences for code formatting in other contexts.

The best solution would be too just support this relatively obvious syntax:

struct Foo.Bar {
}

Of course this would be supported for enum and class in addition to struct.

While "namespaces" are the most frequent use case where I run into this they are certainly not the only use case. This sugar would increase clarity every time we wish to declare a nested type outside of the declaration of the original type. In addition to reducing indentation, this syntax makes the full name of the type clear in its declaration. It also avoids the need to introduce an unwanted scope where members could be added to the containing type.

23 Likes

Yes please, I always wanted this in Swift. (Of course we would extend this functionality later if nesting were added for protocols). Since I personally use extensions for nested types extensively, I'm really tired of this inconvenience. I can't see how this sugar would prevent Swift from implementing other features. That said this sugar is definitely is the right way to go.

@anandabits please also consider this very old thread, especially the last post.

I would also like this for function definitions.

func Foo.bar() {
    //…
}

Kotlin provides a syntax just like this.

I can understand why this may be rejected, the extra level of indentation is informative, but I would enjoy it.

1 Like

I'm not sure how I feel about this at the language level yet, but I'll note that this will probably be difficult to implement because of the pervasive use of lexical DeclContexts throughout the compiler, particularly for lookup purposes. That doesn't say whether it's a good or bad feature, of course.

3 Likes

Bummer, not knowing the compiler I was thinking it might be straightforward since there is an obvious de-sugaring of introducing extensions that only contain the declaration.

+1 to this idea. Needing to indent nested types (which I also use a lot) has always felt distracting / messy to me.

I think the long-standing bug of Swift not parsing nested types in the correct order could be fixed at the same time this is implemented. :) That would be great.

1 Like

I believe you're talking about [SR-631] Extensions in different files do not recognize each other · Issue #43248 · apple/swift · GitHub, which is fixed on master.

4 Likes

I would love to see this, but I would also like to see proper namespaces. I think much this would be alleviated if the language had namespaces/packages/modules (a different kind of "module", where >1 could exist per project)

FWIW, I view namespaces as an orthogonal feature. There are lots of reasons to nest types. Using empty enums to mimic namespaces is just one. Further, if we had namespaces I would still prefer:

namespace Foo {}
struct Foo.Bar {}

over:

namespace Foo {}
extension Foo {
    struct Bar {}
}

So I think this syntactic sugar would be extra handy in the presence of namespaces.

5 Likes

Swift 0.00001 (not really a thing, but you get the idea :-) had this and we removed it as a premature syntax optimization pretty early on (~2012?). That said, I still think it makes sense, and I also personally really dislike nesting that comes with namespacy things like extension and namespace{} in C++.

7 Likes

I’m curious if you have the same implementation concerns as Jordan. Does it require major surgery or is there a relatively simple way to implement the de-sugaring?

I don't know enough about the implementation cost of this, I assume Jordan is right. That said, this is "just code" so it could be fixed...

For reference, this came up earlier this year as well: Syntactic Sugar for nested types in extensions

I'd be really happy to see this in Swift -- the extra level of indentation for nested types makes them feel more heavyweight than they should.

Okay that would be a convenient way for us to write code, but what would the imported API look like, the same or the old way with extensions? We also need to make sure that people don‘t start misinterpret access modifier on such types.

It will be also great if we could do nested struct declarations such as:

struct A {
	let a: Int
	let b: {
		let c: Int
		let d: Int
	}
}

where type of b is sugar declared as:

struct A.B {
	let c: Int
	let d: Int
}

I am also interested on this feature. I have experienced the same scenario with the namespacing, and thought about the same syntactic sugar. I only see benefits on it, and feels something natural at this point.

What is this question referring to?

If you write struct A.B { ... }, how will the imported API be printed by the ASTPrinter?

// Version 1:
struct A.B { ... }

// Version 2:
extension A {
  struct B { ... }
}

I don't know what ASTPrinter does so I can't answer that. :slight_smile: If it's supposed to preserve source structure format obviously the former. If not then I don't know. They are semantically equivalent.

IIRC it's also responsible for printing the API surface if you cmd+click on a module in Xcode.