Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format record type annotations. #1346

Merged
merged 10 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions lib/src/front_end/ast_node_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1511,19 +1511,59 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {

@override
Piece visitRecordTypeAnnotation(RecordTypeAnnotation node) {
throw UnimplementedError();
var namedFields = node.namedFields;
var positionalFields = node.positionalFields;

// Single positional record types always have a trailing comma.
var listStyle = positionalFields.length == 1 && namedFields == null
? const ListStyle(commas: Commas.alwaysTrailing)
: const ListStyle(commas: Commas.trailing);
var builder = DelimitedListBuilder(this, listStyle);

// If all parameters are optional, put the `{` right after `(`.
if (positionalFields.isEmpty && namedFields != null) {
builder.leftBracket(
node.leftParenthesis,
delimiter: namedFields.leftBracket,
);
} else {
builder.leftBracket(node.leftParenthesis);
}

for (var positionalField in positionalFields) {
builder.visit(positionalField);
}

Token? rightDelimiter;
if (namedFields != null) {
// If we have both positional fields and named fields, then we need to add
// the left bracket delimiter before the first named field.
if (positionalFields.isNotEmpty) {
builder.leftDelimiter(namedFields.leftBracket);
}
for (var namedField in namedFields.fields) {
builder.visit(namedField);
}
rightDelimiter = namedFields.rightBracket;
}

builder.rightBracket(node.rightParenthesis, delimiter: rightDelimiter);
return buildPiece((b) {
b.add(builder.build());
b.token(node.question);
});
}

@override
Piece visitRecordTypeAnnotationNamedField(
RecordTypeAnnotationNamedField node) {
throw UnimplementedError();
return createRecordTypeField(node);
}

@override
Piece visitRecordTypeAnnotationPositionalField(
RecordTypeAnnotationPositionalField node) {
throw UnimplementedError();
return createRecordTypeField(node);
}

@override
Expand Down
65 changes: 47 additions & 18 deletions lib/src/front_end/piece_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,24 +225,14 @@ mixin PieceFactory {
var builder = AdjacentBuilder(this);
startFormalParameter(node, builder);
builder.modifier(mutableKeyword);
builder.visit(type);

Piece? typePiece;
if (type != null && name != null) {
typePiece = builder.build();
}

builder.token(fieldKeyword);
builder.token(period);
builder.token(name);

// If we have both a type and name, allow splitting between them.
if (typePiece != null) {
var namePiece = builder.build();
return VariablePiece(typePiece, [namePiece], hasType: true);
}

return builder.build();
return finishTypeAndName(
type,
name,
builder,
mutableKeyword: mutableKeyword,
fieldKeyword: fieldKeyword,
period: period,
);
}

/// Creates a function, method, getter, or setter declaration.
Expand Down Expand Up @@ -542,6 +532,17 @@ mixin PieceFactory {
return builder.build();
}

/// Creates an [AdjacentPiece] for a given record type field.
Piece createRecordTypeField(RecordTypeAnnotationField node) {
// TODO(tall): Format metadata.
if (node.metadata.isNotEmpty) throw UnimplementedError();
return finishTypeAndName(
node.type,
node.name,
AdjacentBuilder(this),
);
}

/// Creates a class, enum, extension, mixin, or mixin application class
/// declaration.
///
Expand Down Expand Up @@ -653,6 +654,34 @@ mixin PieceFactory {
style: const ListStyle(commas: Commas.nonTrailing, splitCost: 3));
}

/// Creates a [VariablePiece] that allows splitting between a type and a name,
/// if they both exist.
///
/// Otherwise, finishes building the existing [AdjacentPiece] with the
/// [builder].
Piece finishTypeAndName(
TypeAnnotation? type, Token? name, AdjacentBuilder builder,
{Token? mutableKeyword, Token? fieldKeyword, Token? period}) {
builder.visit(type);

Piece? typePiece;
if (type != null && name != null) {
typePiece = builder.build();
}

builder.token(fieldKeyword);
builder.token(period);
builder.token(name);

// If we have both a type and name, allow splitting between them.
if (typePiece != null) {
var namePiece = builder.build();
return VariablePiece(typePiece, [namePiece], hasType: true);
}

return builder.build();
}

/// Writes the parts of a formal parameter shared by all formal parameter
/// types: metadata, `covariant`, etc.
void startFormalParameter(
Expand Down
50 changes: 50 additions & 0 deletions test/type/function.stmt
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,56 @@ longMethod({
required int
reallyLongParameterNameWow,
}) {}
>>> Record type with multiple fields in parameter has no trailing comma.
function((TypeName, TypeName) parameter) { ; }
<<<
function(
(TypeName, TypeName) parameter,
) {
;
}
>>> Split single long positional record type field.
function((VeryLongTypeName___________________,) param) {;}
<<<
function(
(
VeryLongTypeName___________________,
) param,
) {
;
}
>>> Split inside parameter list with record type.
function((TypeName, TypeName, TypeName, TypeName, TypeName) record) {;}
<<<
function(
(
TypeName,
TypeName,
TypeName,
TypeName,
TypeName,
) record,
) {
;
}
>>> Single positional has a trailing comma inside parameter list with record type.
function((TypeName,) record) {;}
<<<
function((TypeName,) record) {
;
}
>>> Named parameter has no trailing comma inside parameter list with record type.
function(({TypeName param,}) record) {;}
<<<
function(({TypeName param}) record) {
;
}
>>> Multiple positional fields have no trailing comma in parameter list with record type.
function((TypeName,TypeName,) record,) {;}
<<<
function((TypeName, TypeName) record) {
;
}
>>> Unsplit generic method instantiation.
void main() => id < int > ;
<<<
Expand Down
145 changes: 145 additions & 0 deletions test/type/record.stmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
40 columns |
>>> Empty record.
( ) x;
<<<
() x;
>>> Empty nullable record type.
( ) ? x;
<<<
()? x;
>>> Nullable record type.
( int , bool ) ? x;
<<<
(int, bool)? x;
>>> Single positional field.
( int , ) x;
<<<
(int,) x;
>>> Single named field.
( { int n } ) x;
<<<
({int n}) x;
>>> Named positional fields.
( int value , String label) x;
<<<
(int value, String label) x;
>>> Unnamed positional fields.
( int , String ) x;
<<<
(int, String) x;
>>> Named fields.
( { int value , String label } ) x;
<<<
({int value, String label}) x;
>>> Split between the type and the name.
( VeryVeryLongType_____ veryLongName___________________ , ) x;
<<<
(
VeryVeryLongType_____
veryLongName___________________,
) x;
>>> Split named positional fields.
( int longValue , String veryVeryLongLabel , ) x;
<<<
(
int longValue,
String veryVeryLongLabel,
) x;
>>> Unsplit unnamed positional fields have no trailing comma.
( int , String , ) x;
<<<
(int, String) x;
>>> Split only named fields.
( { int longValue , String veryLongLabel , } ) x;
<<<
({
int longValue,
String veryLongLabel,
}) x;
>>> Empty record types don't split.
someLongFunctionName__________________(() x) {}
<<<
someLongFunctionName__________________(
() x,
) {}
>>> Unsplit short single positional field.
(TypeName,
) x;
<<<
(TypeName,) x;
>>> Unsplit single positional field.
(VeryLongTypeName________________,) x;
<<<
(VeryLongTypeName________________,) x;
>>> Split positional types.
(TypeName,TypeName,TypeName,TypeName) x;
<<<
(
TypeName,
TypeName,
TypeName,
TypeName,
) x;
>>> Split named types.
({TypeName a,TypeName b,TypeName c,TypeName d}) x;
<<<
({
TypeName a,
TypeName b,
TypeName c,
TypeName d,
}) x;
>>> Split named if positional splits.
(TypeName,TypeName,TypeName,TypeName,{TypeName a,TypeName b}) x;
<<<
(
TypeName,
TypeName,
TypeName,
TypeName, {
TypeName a,
TypeName b,
}) x;
>>> Split positional if named splits.
(TypeName,TypeName,{TypeName a,TypeName b,TypeName c,TypeName d}) x;
<<<
(
TypeName,
TypeName, {
TypeName a,
TypeName b,
TypeName c,
TypeName d,
}) x;
>>> Single named field has no trailing comma.
({int n,}) x;
<<<
({int n}) x;
>>> Multiple positional fields have no trailing comma.
(int m, int n,) x;
<<<
(int m, int n) x;
>>> Split outer record if inner record splits.
((TypeName,TypeName,TypeName,TypeName),TypeName) x;
<<<
(
(
TypeName,
TypeName,
TypeName,
TypeName,
),
TypeName,
) x;
>>> Split outer type argument list if inner record splits.
Map<String, (TypeName,TypeName,TypeName,TypeName)> map;
<<<
Map<
String,
(
TypeName,
TypeName,
TypeName,
TypeName,
)
> map;
Loading