[cfe] Handle dot shorthand invocations.
This CL adds the resolution of dot shorthand invocations. At the point of parsing, we can't be sure whether the invocation is a method invocation or a constructor invocation. We'll resolve the name with the given context type to find out. Bug: https://github.com/dart-lang/sdk/issues/59758 Change-Id: I136ab6c7522fe24d98a4613d102089d50c4cd8cd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412800 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Kallen Tu <kallentu@google.com>
This commit is contained in:
@@ -10016,10 +10016,15 @@ class BodyBuilder extends StackListenerImpl
|
||||
assert(checkState(token, [ValueKinds.Selector]));
|
||||
Selector selector = pop() as Selector;
|
||||
if (libraryFeatures.dotShorthands.isEnabled) {
|
||||
// TODO(kallentu): Handle invocations.
|
||||
|
||||
push(forest.createDotShorthandPropertyGet(
|
||||
offsetForToken(token), selector.name));
|
||||
if (selector is InvocationSelector) {
|
||||
// e.g. `.parse(2)`
|
||||
push(forest.createDotShorthandInvocation(
|
||||
offsetForToken(token), selector.name, selector.arguments));
|
||||
} else if (selector is PropertySelector) {
|
||||
// e.g. `.zero`
|
||||
push(forest.createDotShorthandPropertyGet(
|
||||
offsetForToken(token), selector.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,6 +933,11 @@ class Forest {
|
||||
return new DotShorthand(innerExpression)..fileOffset = fileOffset;
|
||||
}
|
||||
|
||||
DotShorthandInvocation createDotShorthandInvocation(
|
||||
int fileOffset, Name name, Arguments arguments) {
|
||||
return new DotShorthandInvocation(name, arguments)..fileOffset = fileOffset;
|
||||
}
|
||||
|
||||
DotShorthandPropertyGet createDotShorthandPropertyGet(
|
||||
int fileOffset, Name name) {
|
||||
return new DotShorthandPropertyGet(name)..fileOffset = fileOffset;
|
||||
|
||||
@@ -3248,6 +3248,38 @@ class DotShorthand extends InternalExpression {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal expression for a dot shorthand head with arguments.
|
||||
/// (e.g. `.parse(42)`).
|
||||
///
|
||||
/// This node could represent a shorthand of a static method or a named
|
||||
/// constructor.
|
||||
class DotShorthandInvocation extends InternalExpression {
|
||||
Name name;
|
||||
|
||||
Arguments arguments;
|
||||
|
||||
DotShorthandInvocation(this.name, this.arguments);
|
||||
|
||||
@override
|
||||
ExpressionInferenceResult acceptInference(
|
||||
InferenceVisitorImpl visitor, DartType typeContext) {
|
||||
return visitor.visitDotShorthandInvocation(this, typeContext);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "DotShorthandInvocation(${toStringInternal()})";
|
||||
}
|
||||
|
||||
@override
|
||||
// Coverage-ignore(suite): Not run.
|
||||
void toTextInternal(AstPrinter printer) {
|
||||
printer.write('.');
|
||||
printer.writeName(name);
|
||||
printer.writeArguments(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal expression for a dot shorthand head with no arguments.
|
||||
/// (e.g. `.zero`).
|
||||
///
|
||||
|
||||
@@ -30,7 +30,7 @@ import '../base/instrumentation.dart'
|
||||
InstrumentationValueForType,
|
||||
InstrumentationValueForTypeArgs;
|
||||
import '../base/problems.dart' as problems
|
||||
show internalProblem, unhandled, unsupported;
|
||||
show internalProblem, unhandled, unsupported, unimplemented;
|
||||
import '../base/uri_offset.dart';
|
||||
import '../codes/cfe_codes.dart';
|
||||
import '../kernel/body_builder.dart' show combineStatements;
|
||||
@@ -12111,6 +12111,63 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
return new ExpressionInferenceResult(rewrittenType, rewrittenExpr);
|
||||
}
|
||||
|
||||
ExpressionInferenceResult visitDotShorthandInvocation(
|
||||
DotShorthandInvocation node, DartType typeContext) {
|
||||
// Use the previously cached context type to determine the declaration
|
||||
// member that we're trying to find.
|
||||
DartType cachedContext = getDotShorthandContext().unwrapTypeSchemaView();
|
||||
Member? member = findInterfaceMember(
|
||||
cachedContext, node.name, node.fileOffset,
|
||||
includeExtensionMethods: false,
|
||||
isSetter: false,
|
||||
isDotShorthand: true)
|
||||
.member;
|
||||
|
||||
Expression expr;
|
||||
if (member is Procedure) {
|
||||
expr = new StaticInvocation(member, node.arguments)
|
||||
..fileOffset = node.fileOffset;
|
||||
} else if (member == null && cachedContext is TypeDeclarationType) {
|
||||
// Couldn't find a static method in the declaration so we'll try and find
|
||||
// a constructor of that name instead.
|
||||
Member? constructor =
|
||||
findConstructor(cachedContext, node.name, node.fileOffset);
|
||||
if (constructor is Constructor) {
|
||||
// TODO(kallentu): Const constructors.
|
||||
expr = new ConstructorInvocation(constructor, node.arguments,
|
||||
isConst: false)
|
||||
..fileOffset = node.fileOffset;
|
||||
} else if (constructor is Procedure) {
|
||||
// [constructor] can be a [Procedure] if we have an extension type
|
||||
// constructor.
|
||||
expr = new StaticInvocation(constructor, node.arguments)
|
||||
..fileOffset = node.fileOffset;
|
||||
} else {
|
||||
// Coverage-ignore-block(suite): Not run.
|
||||
// TODO(kallentu): This is temporary. Build a problem with an error
|
||||
// specific to not being able to find a member named [node.name].
|
||||
problems.unimplemented(
|
||||
'Cannot find dot shorthand member of name ${node.name}',
|
||||
node.fileOffset,
|
||||
helper.uri);
|
||||
}
|
||||
} else {
|
||||
// Coverage-ignore-block(suite): Not run.
|
||||
// TODO(kallentu): This is temporary. Build a problem with an error on the
|
||||
// bad context type.
|
||||
problems.unimplemented(
|
||||
'Cannot find dot shorthand member of name ${node.name} with '
|
||||
'context $cachedContext',
|
||||
node.fileOffset,
|
||||
helper.uri);
|
||||
}
|
||||
|
||||
ExpressionInferenceResult expressionInferenceResult =
|
||||
inferExpression(expr, cachedContext);
|
||||
flowAnalysis.forwardExpression(expressionInferenceResult.expression, node);
|
||||
return expressionInferenceResult;
|
||||
}
|
||||
|
||||
ExpressionInferenceResult visitDotShorthandPropertyGet(
|
||||
DotShorthandPropertyGet node, DartType typeContext) {
|
||||
// Use the previously cached context type to determine the declaration
|
||||
@@ -12123,9 +12180,13 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
|
||||
ExpressionInferenceResult expressionInferenceResult;
|
||||
if (member == null) {
|
||||
// Coverage-ignore-block(suite): Not run.
|
||||
// TODO(kallentu): This is temporary. Build a problem with an error
|
||||
// specific to not being able to find a member named [node.name].
|
||||
throw 'Error: Cannot find dot shorthand member.';
|
||||
problems.unimplemented(
|
||||
'Cannot find dot shorthand member of name ${node.name}',
|
||||
node.fileOffset,
|
||||
helper.uri);
|
||||
} else if (member is Procedure && !member.isGetter) {
|
||||
// Tearoff like `Object.new`;
|
||||
expressionInferenceResult =
|
||||
|
||||
@@ -1169,6 +1169,28 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
|
||||
return defaultTarget;
|
||||
}
|
||||
|
||||
/// Finds a constructor of [type] called [name].
|
||||
Member? findConstructor(TypeDeclarationType type, Name name, int fileOffset) {
|
||||
assert(isKnown(type));
|
||||
|
||||
// TODO(Dart Model team): Seems like an abstraction level issue to require
|
||||
// going from `Class` objects back to builders to find a `Member`.
|
||||
DeclarationBuilder builder;
|
||||
switch (type) {
|
||||
case InterfaceType():
|
||||
builder = engine.hierarchyBuilder.loader
|
||||
.computeClassBuilderFromTargetClass(type.classNode);
|
||||
case ExtensionType():
|
||||
builder = engine.hierarchyBuilder.loader
|
||||
.computeExtensionTypeBuilderFromTargetExtensionType(
|
||||
type.extensionTypeDeclaration);
|
||||
}
|
||||
|
||||
MemberBuilder? constructorBuilder = builder.findConstructorOrFactory(
|
||||
name.text, fileOffset, helper.uri, libraryBuilder);
|
||||
return constructorBuilder?.invokeTarget;
|
||||
}
|
||||
|
||||
/// Finds a member of [receiverType] called [name], and if it is found,
|
||||
/// reports it through instrumentation using [fileOffset].
|
||||
///
|
||||
|
||||
@@ -590,7 +590,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
|
||||
),
|
||||
// 100.0%.
|
||||
"package:front_end/src/kernel/body_builder.dart": (
|
||||
hitCount: 7205,
|
||||
hitCount: 7213,
|
||||
missCount: 0,
|
||||
),
|
||||
// 100.0%.
|
||||
@@ -660,7 +660,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
|
||||
),
|
||||
// 100.0%.
|
||||
"package:front_end/src/kernel/forest.dart": (
|
||||
hitCount: 402,
|
||||
hitCount: 405,
|
||||
missCount: 0,
|
||||
),
|
||||
// 100.0%.
|
||||
@@ -720,7 +720,7 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
|
||||
),
|
||||
// 100.0%.
|
||||
"package:front_end/src/kernel/internal_ast.dart": (
|
||||
hitCount: 551,
|
||||
hitCount: 554,
|
||||
missCount: 0,
|
||||
),
|
||||
// 100.0%.
|
||||
@@ -976,12 +976,12 @@ const Map<String, ({int hitCount, int missCount})> _expect = {
|
||||
),
|
||||
// 100.0%.
|
||||
"package:front_end/src/type_inference/inference_visitor.dart": (
|
||||
hitCount: 8178,
|
||||
hitCount: 8208,
|
||||
missCount: 0,
|
||||
),
|
||||
// 100.0%.
|
||||
"package:front_end/src/type_inference/inference_visitor_base.dart": (
|
||||
hitCount: 2451,
|
||||
hitCount: 2477,
|
||||
missCount: 0,
|
||||
),
|
||||
// 100.0%.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 Color {
|
||||
final int x;
|
||||
Color.red() : x = 1;
|
||||
Color(this.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
Color c = .red();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor red() → self::Color
|
||||
: self::Color::x = 1, super core::Object::•()
|
||||
;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = new self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor red() → self::Color
|
||||
: self::Color::x = 1, super core::Object::•()
|
||||
;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = new self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor red() → self::Color
|
||||
;
|
||||
constructor •(core::int x) → self::Color
|
||||
;
|
||||
}
|
||||
static method main() → void
|
||||
;
|
||||
@@ -0,0 +1,16 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor red() → self::Color
|
||||
: self::Color::x = 1, super core::Object::•()
|
||||
;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = new self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Color {
|
||||
final int x;
|
||||
Color.red() : x = 1;
|
||||
Color(this.x);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class Color {
|
||||
Color(this.x);
|
||||
Color.red() : x = 1;
|
||||
final int x;
|
||||
}
|
||||
|
||||
void main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
extension type IntegerExt(int integer) {
|
||||
IntegerExt.regular(this.integer);
|
||||
}
|
||||
|
||||
void main() {
|
||||
IntegerExt c = .regular(1);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
extension type IntegerExt(core::int integer) {
|
||||
abstract extension-type-member representation-field get integer() → core::int;
|
||||
constructor • = self::IntegerExt|constructor#;
|
||||
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
|
||||
constructor regular = self::IntegerExt|constructor#regular;
|
||||
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#(integer);
|
||||
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#regular(integer);
|
||||
static method main() → void {
|
||||
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
extension type IntegerExt(core::int integer) {
|
||||
abstract extension-type-member representation-field get integer() → core::int;
|
||||
constructor • = self::IntegerExt|constructor#;
|
||||
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
|
||||
constructor regular = self::IntegerExt|constructor#regular;
|
||||
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#(integer);
|
||||
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#regular(integer);
|
||||
static method main() → void {
|
||||
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
extension type IntegerExt(core::int integer) {
|
||||
abstract extension-type-member representation-field get integer() → core::int;
|
||||
constructor • = self::IntegerExt|constructor#;
|
||||
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
|
||||
constructor regular = self::IntegerExt|constructor#regular;
|
||||
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
;
|
||||
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#(integer);
|
||||
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
;
|
||||
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#regular(integer);
|
||||
static method main() → void
|
||||
;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
extension type IntegerExt(core::int integer) {
|
||||
abstract extension-type-member representation-field get integer() → core::int;
|
||||
constructor • = self::IntegerExt|constructor#;
|
||||
constructor tearoff • = self::IntegerExt|constructor#_#new#tearOff;
|
||||
constructor regular = self::IntegerExt|constructor#regular;
|
||||
constructor tearoff regular = self::IntegerExt|constructor#_#regular#tearOff;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#new#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#(integer);
|
||||
static extension-type-member method IntegerExt|constructor#regular(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */ {
|
||||
lowered final self::IntegerExt% /* erasure=core::int, declared=! */ #this = integer;
|
||||
return #this;
|
||||
}
|
||||
static extension-type-member method IntegerExt|constructor#_#regular#tearOff(core::int integer) → self::IntegerExt% /* erasure=core::int, declared=! */
|
||||
return self::IntegerExt|constructor#regular(integer);
|
||||
static method main() → void {
|
||||
self::IntegerExt% /* erasure=core::int, declared=! */ c = self::IntegerExt|constructor#regular(1);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
extension type IntegerExt(int integer) {
|
||||
IntegerExt.regular(this.integer);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
extension type IntegerExt(int integer) {
|
||||
IntegerExt.regular(this.integer);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 Color {
|
||||
final int x;
|
||||
static Color red() => Color(1);
|
||||
Color(this.x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
Color c = .red();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
static method red() → self::Color
|
||||
return new self::Color::•(1);
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
static method red() → self::Color
|
||||
return new self::Color::•(1);
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor •(core::int x) → self::Color
|
||||
;
|
||||
static method red() → self::Color
|
||||
;
|
||||
}
|
||||
static method main() → void
|
||||
;
|
||||
@@ -0,0 +1,15 @@
|
||||
library;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class Color extends core::Object {
|
||||
final field core::int x;
|
||||
constructor •(core::int x) → self::Color
|
||||
: self::Color::x = x, super core::Object::•()
|
||||
;
|
||||
static method red() → self::Color
|
||||
return new self::Color::•(1);
|
||||
}
|
||||
static method main() → void {
|
||||
self::Color c = self::Color::red();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class Color {
|
||||
final int x;
|
||||
static Color red() => Color(1);
|
||||
Color(this.x);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class Color {
|
||||
Color(this.x);
|
||||
final int x;
|
||||
static Color red() => Color(1);
|
||||
}
|
||||
|
||||
void main() {}
|
||||
Reference in New Issue
Block a user