[cfe] Support primary constructors in classes

Part of https://github.com/dart-lang/sdk/issues/61700

Change-Id: I73800eec8ede6f7ca4c1959de2bcd098c974c74c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457760
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Johnni Winther
2025-10-29 03:33:09 -07:00
committed by Commit Queue
parent dc2a0aae7d
commit de2737affe
13 changed files with 311 additions and 116 deletions
@@ -31,7 +31,6 @@ import '../../source/check_helper.dart';
import '../../source/name_scheme.dart';
import '../../source/source_class_builder.dart';
import '../../source/source_constructor_builder.dart';
import '../../source/source_extension_type_declaration_builder.dart';
import '../../source/source_function_builder.dart';
import '../../source/source_library_builder.dart';
import '../../source/source_loader.dart';
@@ -1337,9 +1336,7 @@ class PrimaryConstructorDeclaration
f,
constructorBuilder: constructorBuilder,
libraryBuilder: libraryBuilder,
declarationBuilder:
constructorBuilder.declarationBuilder
as SourceExtensionTypeDeclarationBuilder,
declarationBuilder: constructorBuilder.declarationBuilder,
name: _fragment.name,
nameScheme: nameScheme,
constructorReferences: constructorReferences,
+115 -112
View File
@@ -1825,123 +1825,126 @@ class OutlineBuilder extends StackListenerImpl {
name = identifier.name;
}
if (!forExtensionType) {
int? startOffset = constKeyword?.charOffset ?? nameOffset ?? formalsOffset;
// TODO(johnniwinther): Handle declaring parameters.
if (forExtensionType) {
bool inExtensionType =
declarationContext == DeclarationContext.ExtensionType;
if (formals != null) {
int requiredPositionalCount = 0;
int? firstNamedParameterOffset;
int? firstOptionalPositionalParameterOffset;
for (int i = 0; i < formals.length; i++) {
FormalParameterBuilder formal = formals[i];
if (inExtensionType) {
TypeBuilder type = formal.type;
if (type is FunctionTypeBuilder &&
type.hasFunctionFormalParameterSyntax) {
_compilationUnit.addProblem(
// ignore: lines_longer_than_80_chars
codeExtensionTypePrimaryConstructorFunctionFormalParameterSyntax,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
if (type is ImplicitTypeBuilder) {
_compilationUnit.addProblem(
codeExpectedRepresentationType,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
formal.type = new InvalidTypeBuilderImpl(
formal.fileUri,
formal.fileOffset,
);
}
if (formal.modifiers.containsSyntacticModifiers(
ignoreCovariant: true,
ignoreRequired: true,
)) {
_compilationUnit.addProblem(
codeRepresentationFieldModifier,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
if (formal.isInitializingFormal) {
_compilationUnit.addProblem(
codeExtensionTypePrimaryConstructorWithInitializingFormal,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
}
if (formal.isPositional) {
if (formal.isOptionalPositional) {
firstOptionalPositionalParameterOffset = formal.fileOffset;
} else {
requiredPositionalCount++;
}
}
if (formal.isNamed) {
firstNamedParameterOffset = formal.fileOffset;
}
_builderFactory.addPrimaryConstructorField(
// TODO(johnniwinther): Support annotations on annotations on fields
// defined through a primary constructor. This is not needed for
// extension types where the field is not part of the AST but will
// be needed when primary constructors are generally supported.
metadata: null,
type: formal.type,
name: formal.name,
nameOffset: formal.fileOffset,
);
formals[i] = formal.forPrimaryConstructor(_builderFactory);
}
if (inExtensionType) {
if (firstOptionalPositionalParameterOffset != null) {
_compilationUnit.addProblem(
codeOptionalParametersInExtensionTypeDeclaration,
firstOptionalPositionalParameterOffset,
1,
uri,
);
} else if (firstNamedParameterOffset != null) {
_compilationUnit.addProblem(
codeNamedParametersInExtensionTypeDeclaration,
firstNamedParameterOffset,
1,
uri,
);
} else if (requiredPositionalCount == 0) {
_compilationUnit.addProblem(
codeExpectedRepresentationField,
charOffset,
1,
uri,
);
} else if (formals.length > 1) {
_compilationUnit.addProblem(
codeMultipleRepresentationFields,
charOffset,
1,
uri,
);
}
}
}
} else {
reportIfNotEnabled(
libraryFeatures.declaringConstructors,
beginToken.charOffset,
noLength,
);
// TODO(johnniwinther): Support primary constructors in general.
return;
}
int? startOffset = constKeyword?.charOffset ?? nameOffset ?? formalsOffset;
bool inExtensionType =
declarationContext == DeclarationContext.ExtensionType;
if (formals != null) {
int requiredPositionalCount = 0;
int? firstNamedParameterOffset;
int? firstOptionalPositionalParameterOffset;
for (int i = 0; i < formals.length; i++) {
FormalParameterBuilder formal = formals[i];
if (inExtensionType) {
TypeBuilder type = formal.type;
if (type is FunctionTypeBuilder &&
type.hasFunctionFormalParameterSyntax) {
_compilationUnit.addProblem(
// ignore: lines_longer_than_80_chars
codeExtensionTypePrimaryConstructorFunctionFormalParameterSyntax,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
if (type is ImplicitTypeBuilder) {
_compilationUnit.addProblem(
codeExpectedRepresentationType,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
formal.type = new InvalidTypeBuilderImpl(
formal.fileUri,
formal.fileOffset,
);
}
if (formal.modifiers.containsSyntacticModifiers(
ignoreCovariant: true,
ignoreRequired: true,
)) {
_compilationUnit.addProblem(
codeRepresentationFieldModifier,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
if (formal.isInitializingFormal) {
_compilationUnit.addProblem(
codeExtensionTypePrimaryConstructorWithInitializingFormal,
formal.fileOffset,
formal.name.length,
formal.fileUri,
);
}
}
if (formal.isPositional) {
if (formal.isOptionalPositional) {
firstOptionalPositionalParameterOffset = formal.fileOffset;
} else {
requiredPositionalCount++;
}
}
if (formal.isNamed) {
firstNamedParameterOffset = formal.fileOffset;
}
_builderFactory.addPrimaryConstructorField(
// TODO(johnniwinther): Support annotations on annotations on fields
// defined through a primary constructor. This is not needed for
// extension types where the field is not part of the AST but will
// be needed when primary constructors are generally supported.
metadata: null,
type: formal.type,
name: formal.name,
nameOffset: formal.fileOffset,
);
formals[i] = formal.forPrimaryConstructor(_builderFactory);
}
if (inExtensionType) {
if (firstOptionalPositionalParameterOffset != null) {
_compilationUnit.addProblem(
codeOptionalParametersInExtensionTypeDeclaration,
firstOptionalPositionalParameterOffset,
1,
uri,
);
} else if (firstNamedParameterOffset != null) {
_compilationUnit.addProblem(
codeNamedParametersInExtensionTypeDeclaration,
firstNamedParameterOffset,
1,
uri,
);
} else if (requiredPositionalCount == 0) {
_compilationUnit.addProblem(
codeExpectedRepresentationField,
charOffset,
1,
uri,
);
} else if (formals.length > 1) {
_compilationUnit.addProblem(
codeMultipleRepresentationFields,
charOffset,
1,
uri,
);
}
if (declarationContext == DeclarationContext.Enum) {
// TODO(johnniwinther): Support primary constructors in enums.
return;
}
}
@@ -0,0 +1,15 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class C1() {}
class const C2() {}
class C3() {
final int? i; // Error
}
class const C4() { // Error
int? i;
}
@@ -0,0 +1,41 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:10:14: Error: Final field 'i' is not initialized.
// Try to initialize the field in the declaration or in every constructor.
// final int? i; // Error
// ^
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:13:15: Error: Constructor is marked 'const' so all fields must be final.
// class const C4() { // Error
// ^
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:14:8: Context: Field isn't final, but constructor is 'const'.
// int? i;
// ^
//
import self as self;
import "dart:core" as core;
class C1 extends core::Object {
constructor •() → self::C1
: super core::Object::•()
;
}
class C2 extends core::Object /*hasConstConstructor*/ {
const constructor •() → self::C2
: super core::Object::•()
;
}
class C3 extends core::Object {
final field core::int? i = null;
constructor •() → self::C3
: super core::Object::•()
;
}
class C4 extends core::Object /*hasConstConstructor*/ {
field core::int? i = null;
const constructor •() → self::C4
: super core::Object::•()
;
}
@@ -0,0 +1,41 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:10:14: Error: Final field 'i' is not initialized.
// Try to initialize the field in the declaration or in every constructor.
// final int? i; // Error
// ^
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:13:15: Error: Constructor is marked 'const' so all fields must be final.
// class const C4() { // Error
// ^
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:14:8: Context: Field isn't final, but constructor is 'const'.
// int? i;
// ^
//
import self as self;
import "dart:core" as core;
class C1 extends core::Object {
constructor •() → self::C1
: super core::Object::•()
;
}
class C2 extends core::Object /*hasConstConstructor*/ {
const constructor •() → self::C2
: super core::Object::•()
;
}
class C3 extends core::Object {
final field core::int? i = null;
constructor •() → self::C3
: super core::Object::•()
;
}
class C4 extends core::Object /*hasConstConstructor*/ {
field core::int? i = null;
const constructor •() → self::C4
: super core::Object::•()
;
}
@@ -0,0 +1,24 @@
library;
import self as self;
import "dart:core" as core;
class C1 extends core::Object {
constructor •() → self::C1
;
}
class C2 extends core::Object /*hasConstConstructor*/ {
const constructor •() → self::C2
: super core::Object::•()
;
}
class C3 extends core::Object {
final field core::int? i;
constructor •() → self::C3
;
}
class C4 extends core::Object /*hasConstConstructor*/ {
field core::int? i;
const constructor •() → self::C4
: super core::Object::•()
;
}
@@ -0,0 +1,41 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:10:14: Error: Final field 'i' is not initialized.
// Try to initialize the field in the declaration or in every constructor.
// final int? i; // Error
// ^
//
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:13:15: Error: Constructor is marked 'const' so all fields must be final.
// class const C4() { // Error
// ^
// pkg/front_end/testcases/declaring_constructors/class_primary_constructor.dart:14:8: Context: Field isn't final, but constructor is 'const'.
// int? i;
// ^
//
import self as self;
import "dart:core" as core;
class C1 extends core::Object {
constructor •() → self::C1
: super core::Object::•()
;
}
class C2 extends core::Object /*hasConstConstructor*/ {
const constructor •() → self::C2
: super core::Object::•()
;
}
class C3 extends core::Object {
final field core::int? i = null;
constructor •() → self::C3
: super core::Object::•()
;
}
class C4 extends core::Object /*hasConstConstructor*/ {
field core::int? i = null;
const constructor •() → self::C4
: super core::Object::•()
;
}
@@ -0,0 +1,11 @@
class C1() {}
class const C2() {}
class C3() {
final int? i;
}
class const C4() {
int? i;
}
@@ -0,0 +1,11 @@
class C1() {}
class C3() {
final int? i;
}
class const C2() {}
class const C4() {
int? i;
}
@@ -18,6 +18,9 @@ import "dart:core" as core;
import "org-dartlang-testcase:///main_lib.dart";
class A extends core::Object {
constructor •(core::String foo) → self::A
: super core::Object::•()
;
static factory redirectingFactoryToSubtype() → self::A
return invalid-expression "pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart:8:45: Error: The constructor function type 'E Function()' isn't a subtype of 'A Function()'.
- 'A' is from 'pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart'.
@@ -18,6 +18,9 @@ import "dart:core" as core;
import "org-dartlang-testcase:///main_lib.dart";
class A extends core::Object {
constructor •(core::String foo) → self::A
: super core::Object::•()
;
static factory redirectingFactoryToSubtype() → self::A
return invalid-expression "pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart:8:45: Error: The constructor function type 'E Function()' isn't a subtype of 'A Function()'.
- 'A' is from 'pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart'.
@@ -18,6 +18,8 @@ import "dart:core" as core;
import "org-dartlang-testcase:///main_lib.dart";
class A extends core::Object {
constructor •(core::String foo) → self::A
;
static factory redirectingFactoryToSubtype() → self::A
return invalid-expression "pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart:8:45: Error: The constructor function type 'E Function()' isn't a subtype of 'A Function()'.
- 'A' is from 'pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart'.
@@ -18,6 +18,9 @@ import "dart:core" as core;
import "org-dartlang-testcase:///main_lib.dart";
class A extends core::Object {
constructor •(core::String foo) → self::A
: super core::Object::•()
;
static factory redirectingFactoryToSubtype() → self::A
return invalid-expression "pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart:8:45: Error: The constructor function type 'E Function()' isn't a subtype of 'A Function()'.
- 'A' is from 'pkg/front_end/testcases/extension_types/with_dependencies/issue53209_2/main.dart'.