Skip to content

Out-of-line destructor syntax ambiguity #4999

@jonmeow

Description

@jonmeow

Summary of issue:

The accepted destructor syntax includes out-of-line definitions such as:

destructor MyClass [addr self: Self*] { ... }

The implicit parameter here could be interpreted as either an implicit parameter for MyClass or an implicit parameter for the destructor. How should ambiguities like this be resolved?

Details:

The full example is:

class MyClass {
  destructor [addr self: Self*];
}
destructor MyClass [addr self: Self*] { ... }

For comparison, note a generic might look like:

class GenericClass[T:! type](N:! T) { ... }
destructor GenericClass[T:! type](N:! T) [addr self: Self*] { ... }

Destructors were adopted in #1154, and 2021-08-03 notes are referenced there but don't seem to get into detail on the out-of-line syntax.

I can offer are a few options for resolving the ambiguity:

  1. Use arbitrary lookahead to see what is after the [].

For the toolchain, in tokenization we will have found the closing ] and we can parse differently based on whether the { follows the ]. Note this may have effects on error recovery, and while it would be efficient in the toolchain, may be more difficult to implement in third-party parsing systems, such as tree-sitter.

This option would also mean that generic types must always have explicit parameters if they have implicit parameters, which may affect other open questions. Because the { would be disambiguating, it would also mean a typo of adding () (as in destructor MyClass [addr self: Self*]()) would lead to diagnostic as a generic type, not incorrectly adding () after a destructor.

Note, I'm not pursuing this approach right now because I believe we don't want arbitrary lookahead to be required.

  1. Require a . before the destructor's implicit parameters, similar to qualified names.

The . indicates a separation of name components. In the context of a destructor introducer, we can treat the final name component in a special way.

For example:

destructor MyClass.[addr self: Self*] { ... }
destructor GenericClass[T:! type](N:! T).[addr self: Self*] { ... }
  1. Switch to a more fn-like declaration, using destructor as the name.

This has benefits of aligning more with fn parsing, and destructor still cleanly disambiguates versus a regular function. In this approach, the destructor keyword takes the place of the function name and can make the explicit parameters disallowed.

Note, I actually thought we'd discussed this approach in the past, but I can't find it from #1154.

For example:

class MyClass {
  fn destructor[addr self: Self*];
}
fn MyClass.destructor[addr self: Self*] { ... }

Any other information that you want to share?

I have a half-working destructor parse, and it was specifically looking at making the qualified name optional that made me notice the challenge here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    leads questionA question for the leads team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions