Skip to content

Conversation

@eernstg
Copy link
Member

@eernstg eernstg commented Oct 22, 2025

See #4485 for background information.

This PR changes the feature specification of declaring constructors such that its grammar rules support the new style of constructor declarations (like new(); and const factory name() = D;).

It handles the ambiguity among factory(); being a method or a factory constructor declaration whose name is the name of the enclosing class/mixin-class/enum/extension-type declaration by making factory a reserved word.

@eernstg eernstg requested a review from munificent October 22, 2025 10:36
@eernstg
Copy link
Member Author

eernstg commented Oct 22, 2025

Testing the libraries in tests/language, the fact that factory is now a reserved word gives rise to 23 new parsing failures (we already have some failures because of parameters declared as var p). Apparently, there are no methods named factory here.

A couple of errors arise because the test specifically tests the treatment of built-in identifiers and reserved words, so that's to be expected.

The remaining errors are all concerned with the use of factory as the second part of a constructor name (as in class C { factory C.factory() => ...; }). This could occur in real code, but it seems likely that it is a more natural choice in test code, and not so common in production.

We could try out the radical approach (making factory a reserved word) in internal code and see if it causes non-trivial breakage (a quick search yields about 12 constructors whose name is of the form C.factory, apparently almost all of them in testing code). Alternatively, we could allow factory everywhere except as a type name and as the name of a method (which yields minimal breakage).

@eernstg eernstg force-pushed the spec_new_constructors_oct25 branch from c1abdba to 19471aa Compare October 23, 2025 13:26
Copy link
Member

@munificent munificent left a comment

Choose a reason for hiding this comment

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

This looks great!

@eernstg eernstg merged commit c8ff69d into main Oct 30, 2025
4 checks passed
@eernstg eernstg deleted the spec_new_constructors_oct25 branch October 30, 2025 13:34
Comment on lines +726 to +729

A factory constructor declaration of the form `factory C(...` where `C`
is the name of the enclosing class, mixin class, enum, or extension type is
treated as if `C` had been omitted.
Copy link
Member

Choose a reason for hiding this comment

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

Would it be more direct to say that it the grammar factory C(... is not allowed to parse as a new-style constructor when C is the name of the surrounding thing.

Then there is no grammar ambiguity, and the syntax only parses as an old-style factory constructor. (And if we ever remove the old-style grammar, we won't still accept factory C(), because we forget to remove this special-case. Better to not have it to begin with.)

| 'CONST'? <factoryConstructorHead> <formalParameterList> '='
<constructorDesignation>; // New form.
<constantConstructorSignature> ::= // Modified rule.
Copy link
Member

@lrhn lrhn Oct 31, 2025

Choose a reason for hiding this comment

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

Also declared on line 617.

<redirectingFactoryConstructorSignature> ::= // Modified rule.
'const'? 'factory' <constructorName> <formalParameterList> '='
<constructorDesignation> // Old form.
| 'CONST'? <factoryConstructorHead> <formalParameterList> '='
Copy link
Member

@lrhn lrhn Oct 31, 2025

Choose a reason for hiding this comment

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

'CONST' -> 'const'

Same, could then be:

  'const'? <factoryDesignator> <formalParameterList> '='

... which is just <factoryConstructorSignature> '=', right?

*Another special exception is introduced with factory constructors in order
to avoid breaking existing code:*

A factory constructor declaration of the form `factory C(...` where `C`
Copy link
Member

@lrhn lrhn Oct 31, 2025

Choose a reason for hiding this comment

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

Or const factory C(....

It needs to be said. 😞

treated as if `C` had been omitted.

*Without this special rule, such a declaration would declare a constructor
named `C.C`. With this rule it declares a constructor named `C`, which
Copy link
Member

Choose a reason for hiding this comment

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

Without this special rule, the grammar would be ambiguous.
And because the rule for the old form is first, it would parse as the old form.
Which means this sentence isn't needed, the grammar would parse it that way anyway.

... until we introduce static extension constructors which can only use the new syntax,
and if that's restricted at the grammar level, the syntax would then start meaning C.C. So let's not do the restriction at the grammar level, allow both to be parsed, then say it's an error to use the old style syntax.

<declaringConstructorSignature> ::= // New rule.
'this' ('.' <identifierOrNew>)? <declaringParameterList>?;
'this' <identifier>? <declaringParameterList>?;
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be 'this' (<identifier>? <declaringParameterList>)?;.

You can't have an identifier without a parameter list.

The const, name-identifier and parameter list either all go in the class header or all in an in-body declaration.

<factoryConstructorSignature> ::= // Modified rule.
'const'? 'factory' <constructorName> <formalParameterList> // Old form.
| 'const'? <factoryConstructorHead> <formalParameterList>; // New form.
Copy link
Member

@lrhn lrhn Oct 31, 2025

Choose a reason for hiding this comment

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

(Would it make sense to make this:

<factoryConstructorSignature> ::=
    'const'? <factoryDesignator> <formalParameterList>

<factoryDesignator> ::=
       'factory' <constructorName>
    | <factoryConstructorHead>

(Strawman production name!)
It seems that the rest of the productions are the same, and mean the same thing,
so we can abstract only the part that actually is different.)

<constantConstructorSignature> ::= // Modified rule.
: 'const' <constructorName> <formalParameterList> // Old form.
| 'const' <constructorHead> <formalParameterList> // New form.
| <declaringConstantConstructorSignature>;
Copy link
Member

Choose a reason for hiding this comment

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

If we didn't use <declaringFormalParameterList> for <declaringConstrntConstructorSignature>, but just said that it is an error to have a declaring parameter anywhere else, this could also be:

<constantConstructorSignature> ::= 
     'const' <constructorDesignator> <formalParameterList>;

<constructorDesignator> ::=
       <constructorName>   // old form
    | <constructorHead>    // new form
    | <declaringConstructorHead> // also new
    ;

<declaringConstructorHead> ::=
    'this' <identifier>?;

which also avoids repeating things that mean the same thing between different productions.


This ambiguity is resolved as follows: When a Dart parser expects to parse
a `<memberDeclaration>`, and the first token is `factory`, it proceeds to
parse the following input as a factory constructor.
Copy link
Member

Choose a reason for hiding this comment

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

(Could add commentary like "This is similar to how a statement starting with switch or { are parsed as a switch statement or block, and never as an expression statement.".)

*It introduces a breaking change in the grammar, which implies that
developers must explicitly enable it. In particular, `factory() {}` in a
class body used to be a method declaration. With this feature it will be
a factory constructor declaration.*
Copy link
Member

Choose a reason for hiding this comment

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

The feature also disallows var x, final x and final T x as parameters in all non-declaring constructors (Line 122/130.), which is much more far-reaching and potentially breaking.

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.

4 participants