Skip to content

Unified: loosen AST schema - #22498

Draft
asgerf wants to merge 11 commits into
github:mainfrom
asgerf:unified/loosen-schema
Draft

Unified: loosen AST schema#22498
asgerf wants to merge 11 commits into
github:mainfrom
asgerf:unified/loosen-schema

Conversation

@asgerf

@asgerf asgerf commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Refactors the AST to be more loosely typed so that it becomes feasible to translate of various cases of ambiguous syntax into a valid AST. Previously we ended up with invalid ASTs in many cases, often manifesting as "holes" in the AST.

Changes

  • Expr, Pattern, and TypeExpr are merged into a single type, called Expr. The union types are merged and some of their concrete types like TupleExpr and TuplePattern are merged as well.
  • Bare Identifiers may now appear in directly in expr/pattern/type context.
  • When an identifier appears in pattern context, the name-binding plugin is responsible for classifying it as a binding or a reference. It defaults to being a binding unless the name-binding plugin stays otherwise.
    • We could let the extractor classify identifiers when it is able to do so, by introducing subclasses of identifier, giving languages the freedom to classify names where it happens to be easiest. But there isn't a strong need right now, and I see it as a relatively safe refactoring that we can do in the future.
  • Fields containing an Identifier are also renamed to clarify the distinction between two kinds of getters we see in several contexts. We now use the convention:
    • getName() -> string
    • getNameNode() -> Identifier.

Ambiguous syntax

As mentioned, the motivation for loosening the AST is to be able to handle ambiguous syntax. For example, in a pattern such as case Foo.bar(3), we cannot distinguish between these interpretations at translation-time:

  • If Foo.bar resolves to an enum case with data parameters: this is a constructor pattern, destructuring the incoming value.
  • If Foo.bar resolves to a static method: this is an expr-equality pattern comparing the incoming value to the return value of the static call.

With a more loosely-typed AST, it is mapped to a CallExpr in both cases, and we can recover the distinction by introducing subclasses after the name-binding pass (not done in this PR).

See internal issue for more detailed write-up about such cases of ambiguous syntax in Swift.

ExprEqualityPattern

Note that we not longer insert ExprEqualityPattern at the boundary between expressions and patterns. We previously made a best-effort attempt at this, but produced a malformed AST when we failed to insert it correctly. It might make sense to re-introduce this class and insert it heuristically where we can, but let's wait until we have a more pressing use-case.

final override F::AstNode getAFieldOrChild() { unified_expr_equality_pattern_def(this, result) }
}
/** Gets the node corresponding to the field `modifier`. */
final F::Modifier getModifier(int i) { unified_expr_pattern_modifier(this, i, result) }

/** Gets the node corresponding to the field `type_argument`. */
final F::TypeExpr getTypeArgument(int i) {
final F::Expr getTypeArgument(int i) {

/** Gets the node corresponding to the field `modifier`. */
final F::Modifier getModifier(int i) { unified_name_pattern_modifier(this, i, result) }
final F::Modifier getModifier(int i) { unified_named_pattern_modifier(this, i, result) }
@asgerf

asgerf commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot investigate the failures in 'unified language tests' fix the underlying problems. (Ignore CI checks about QLdoc)

@asgerf
asgerf force-pushed the unified/loosen-schema branch from 358fe1a to 99ffe04 Compare September 4, 2026 11:17
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.

2 participants