Skip to content

Commit 8a236c0

Browse files
authored
Rework how function types, type annotations, and typedefs are handled. (#1493)
In the process of trying to simplify the number of Piece classes, I noticed that FunctionPiece basically exists to split between the return type annotation and the rest of a function. That's pretty similar to how VariablePiece handles splitting between a variable's type annotation and name. I unified those, but then it made typedefs format funny. Looking into it, it's because typedefs have `=` but weren't using AssignPiece. Instead, they just never allowed splitting at the `=`. So I made that uniform with the rest of the style and used AssignPiece here. That led to some weird looking code in cases like: Function(int someParameter) someVariable; If that line doesn't fit, the formatter has to decide whether to split inside the type annotation or between the type and variable. There were different heuristics for return types followed by function names versus type annotations followed by variable names. Unifying those led to some weird output like: Function( int someParameter, ) Function( int someParameter, ) someVariable; This is a variable whose type is a function that returns another function. Admittedly, no one writes code like this. Ultimately, I felt like the weirdness was from allowing the variable name to hang off the end of a split annotation. In most places in the style, if an inner construct splits, the outer one does too. So I changed that. If a type annotation splits, then we also split after the type annotation too. That means after a return type before a function name, or after a variable type before a variable. So instead of allowing: SomeGenericClass< LongTypeArgument, AnotherLongTypeArgument > variable; The split in the type argument list forces the variable name to split too: SomeGenericClass< LongTypeArgument, AnotherLongTypeArgument > variable; I think I like how this looks a little more but I'm not sure. In practice, it doesn't matter much because it's rare for a type annotation to be long enough to split, but it does happen. For what it's worth, it's consistent with metadata on variables. If the metadata splits, we also split before the variable too: @SomeMetadata( 'annotation argument', 'another argument', ) variable; Thoughts?
1 parent 0a78529 commit 8a236c0

File tree

16 files changed

+428
-410
lines changed

16 files changed

+428
-410
lines changed

lib/src/front_end/ast_node_visitor.dart

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
223223

224224
@override
225225
void visitBlockFunctionBody(BlockFunctionBody node) {
226+
pieces.space();
226227
writeFunctionBodyModifiers(node);
227228
pieces.visit(node.block);
228229
}
@@ -430,7 +431,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
430431
initializers = createCommaSeparated(node.initializers);
431432
}
432433

433-
var body = createFunctionBody(node.body);
434+
var body = nodePiece(node.body);
434435

435436
pieces.add(ConstructorPiece(header, parameters, body,
436437
canSplitParameters: node.parameters.parameters
@@ -595,6 +596,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
595596
@override
596597
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
597598
var operatorPiece = pieces.build(() {
599+
pieces.space();
598600
writeFunctionBodyModifiers(node);
599601
pieces.token(node.functionDefinition);
600602
});
@@ -860,12 +862,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
860862
pieces.token(node.name);
861863
pieces.visit(node.typeParameters);
862864
pieces.space();
863-
pieces.token(node.equals);
864-
// Don't bother allowing splitting after the `=`. It's always better to
865-
// split inside the type parameter, type argument, or parameter lists of
866-
// the typedef or the aliased type.
867-
pieces.space();
868-
pieces.visit(node.type);
865+
pieces.add(AssignPiece(tokenPiece(node.equals), nodePiece(node.type)));
869866
pieces.token(node.semicolon);
870867
});
871868
}
@@ -1324,6 +1321,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
13241321

13251322
@override
13261323
void visitNativeFunctionBody(NativeFunctionBody node) {
1324+
pieces.space();
13271325
pieces.token(node.nativeKeyword);
13281326
pieces.visit(node.stringLiteral, spaceBefore: true);
13291327
pieces.token(node.semicolon);
@@ -1859,43 +1857,44 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
18591857

18601858
@override
18611859
void visitVariableDeclarationList(VariableDeclarationList node) {
1862-
var header = pieces.build(
1863-
metadata: node.metadata,
1860+
pieces.withMetadata(node.metadata,
18641861
// If the variable is part of a for loop, it looks weird to force the
18651862
// metadata to split since it's in a sort of expression-ish location:
18661863
//
18671864
// for (@meta var x in list) ...
18681865
inlineMetadata: _parentContext == NodeContext.forLoopVariable, () {
1869-
pieces.modifier(node.lateKeyword);
1870-
pieces.modifier(node.keyword);
1871-
pieces.visit(node.type);
1872-
});
1866+
var header = pieces.build(() {
1867+
pieces.modifier(node.lateKeyword);
1868+
pieces.modifier(node.keyword);
1869+
pieces.visit(node.type);
1870+
});
18731871

1874-
var variables = <Piece>[];
1875-
for (var variable in node.variables) {
1876-
if ((variable.equals, variable.initializer)
1877-
case (var equals?, var initializer?)) {
1878-
var variablePiece = tokenPiece(variable.name);
1872+
var variables = <Piece>[];
1873+
for (var variable in node.variables) {
1874+
if ((variable.equals, variable.initializer)
1875+
case (var equals?, var initializer?)) {
1876+
var variablePiece = tokenPiece(variable.name);
18791877

1880-
var equalsPiece = pieces.build(() {
1881-
pieces.space();
1882-
pieces.token(equals);
1883-
});
1878+
var equalsPiece = pieces.build(() {
1879+
pieces.space();
1880+
pieces.token(equals);
1881+
});
18841882

1885-
var initializerPiece = nodePiece(initializer,
1886-
commaAfter: true, context: NodeContext.assignment);
1883+
var initializerPiece = nodePiece(initializer,
1884+
commaAfter: true, context: NodeContext.assignment);
18871885

1888-
variables.add(AssignPiece(
1889-
left: variablePiece,
1890-
equalsPiece,
1891-
initializerPiece,
1892-
canBlockSplitRight: initializer.canBlockSplit));
1893-
} else {
1894-
variables.add(tokenPiece(variable.name, commaAfter: true));
1886+
variables.add(AssignPiece(
1887+
left: variablePiece,
1888+
equalsPiece,
1889+
initializerPiece,
1890+
canBlockSplitRight: initializer.canBlockSplit));
1891+
} else {
1892+
variables.add(tokenPiece(variable.name, commaAfter: true));
1893+
}
18951894
}
1896-
}
18971895

1898-
pieces.add(VariablePiece(header, variables, hasType: node.type != null));
1896+
pieces.add(VariablePiece(header, variables, hasType: node.type != null));
1897+
});
18991898
}
19001899

19011900
@override

lib/src/front_end/piece_factory.dart

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import '../piece/assign.dart';
1010
import '../piece/clause.dart';
1111
import '../piece/control_flow.dart';
1212
import '../piece/for.dart';
13-
import '../piece/function.dart';
1413
import '../piece/if_case.dart';
1514
import '../piece/infix.dart';
1615
import '../piece/list.dart';
@@ -570,49 +569,56 @@ mixin PieceFactory {
570569
TypeParameterList? typeParameters,
571570
FormalParameterList? parameters,
572571
required FunctionBody body}) {
573-
void writeModifiers() {
574-
for (var keyword in modifiers) {
575-
pieces.modifier(keyword);
576-
}
577-
}
578-
579572
// Create a piece to attach metadata to the function.
580573
pieces.withMetadata(metadata, () {
581-
Piece? returnTypePiece;
582-
// Create a piece for the return type if there is one.
583-
if (returnType != null) {
584-
returnTypePiece = pieces.build(() {
585-
writeModifiers();
586-
pieces.visit(returnType);
587-
});
588-
}
589-
590-
var signature = pieces.build(() {
574+
writeFunctionAndReturnType(modifiers, returnType, () {
591575
// If there's no return type, attach modifiers to the signature.
592-
if (returnType == null) writeModifiers();
576+
if (returnType == null) {
577+
for (var keyword in modifiers) {
578+
pieces.modifier(keyword);
579+
}
580+
}
593581

594582
pieces.modifier(operatorKeyword);
595583
pieces.modifier(propertyKeyword);
596584
pieces.token(name);
597585
pieces.visit(typeParameters);
598586
pieces.visit(parameters);
587+
pieces.visit(body);
599588
});
589+
});
590+
}
591+
592+
/// Writes a return type followed by either a function signature (when writing
593+
/// a function type annotation or function-typed formal) or a signature and a
594+
/// body (when writing a function declaration).
595+
///
596+
/// The [writeFunction] callback should write the function's signature and
597+
/// body if there is one.
598+
///
599+
/// If there is no return type, invokes [writeFunction] directly and returns.
600+
/// Otherwise, writes the return type and function and wraps them in a piece
601+
/// to allow splitting after the return type.
602+
void writeFunctionAndReturnType(List<Token?> modifiers,
603+
TypeAnnotation? returnType, void Function() writeFunction) {
604+
if (returnType == null) {
605+
writeFunction();
606+
return;
607+
}
600608

601-
var bodyPiece = createFunctionBody(body);
609+
var returnTypePiece = pieces.build(() {
610+
for (var keyword in modifiers) {
611+
pieces.modifier(keyword);
612+
}
602613

603-
pieces.add(FunctionPiece(returnTypePiece, signature,
604-
isReturnTypeFunctionType: returnType is GenericFunctionType,
605-
body: bodyPiece));
614+
pieces.visit(returnType);
606615
});
607-
}
608616

609-
/// Creates a piece for a function, method, or constructor body.
610-
Piece createFunctionBody(FunctionBody body) {
611-
return pieces.build(() {
612-
// Don't put a space before `;` bodies.
613-
if (body is! EmptyFunctionBody) pieces.space();
614-
pieces.visit(body);
617+
var signature = pieces.build(() {
618+
writeFunction();
615619
});
620+
621+
pieces.add(VariablePiece(returnTypePiece, [signature], hasType: true));
616622
}
617623

618624
/// If [parameter] has a [defaultValue] then writes a piece for the parameter
@@ -660,30 +666,9 @@ mixin PieceFactory {
660666
{FormalParameter? parameter,
661667
Token? fieldKeyword,
662668
Token? period}) {
663-
// If the type is a function-typed parameter with a default value, then
664-
// grab the default value from the parent node.
665-
(Token separator, Expression value)? defaultValueRecord;
666-
if (parameter?.parent
667-
case DefaultFormalParameter(:var separator?, :var defaultValue?)) {
668-
defaultValueRecord = (separator, defaultValue);
669-
}
670-
671669
var metadata = parameter?.metadata ?? const <Annotation>[];
672670
pieces.withMetadata(metadata, inlineMetadata: true, () {
673-
Piece? returnTypePiece;
674-
if (returnType != null) {
675-
returnTypePiece = pieces.build(() {
676-
// Attach any parameter modifiers to the return type.
677-
if (parameter != null) {
678-
pieces.modifier(parameter.requiredKeyword);
679-
pieces.modifier(parameter.covariantKeyword);
680-
}
681-
682-
pieces.visit(returnType);
683-
});
684-
}
685-
686-
var signature = pieces.build(() {
671+
void write() {
687672
// If there's no return type, attach the parameter modifiers to the
688673
// signature.
689674
if (parameter != null && returnType == null) {
@@ -697,10 +682,11 @@ mixin PieceFactory {
697682
pieces.visit(typeParameters);
698683
pieces.visit(parameters);
699684
pieces.token(question);
700-
});
685+
}
701686

702-
var function = FunctionPiece(returnTypePiece, signature,
703-
isReturnTypeFunctionType: returnType is GenericFunctionType);
687+
var returnTypeModifiers = parameter != null
688+
? [parameter.requiredKeyword, parameter.covariantKeyword]
689+
: const <Token?>[];
704690

705691
// TODO(rnystrom): It would be good if the AssignPiece created for the
706692
// default value could treat the parameter list on the left-hand side as
@@ -710,7 +696,19 @@ mixin PieceFactory {
710696
// practice, it doesn't really matter since function-typed formals are
711697
// deprecated, default values on function-typed parameters are rare, and
712698
// when both occur, they rarely split.
713-
writeDefaultValue(function, defaultValueRecord);
699+
// If the type is a function-typed parameter with a default value, then
700+
// grab the default value from the parent node and attach it to the
701+
// function.
702+
if (parameter?.parent
703+
case DefaultFormalParameter(:var separator?, :var defaultValue?)) {
704+
var function = pieces.build(() {
705+
writeFunctionAndReturnType(returnTypeModifiers, returnType, write);
706+
});
707+
708+
writeDefaultValue(function, (separator, defaultValue));
709+
} else {
710+
writeFunctionAndReturnType(returnTypeModifiers, returnType, write);
711+
}
714712
});
715713
}
716714

lib/src/piece/function.dart

Lines changed: 0 additions & 81 deletions
This file was deleted.

lib/src/piece/variable.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import 'piece.dart';
88
/// A variable declaration.
99
///
1010
/// Used for local variable declaration statements, top-level variable
11-
/// declarations and field declarations.
11+
/// declarations and field declarations. Also used to handle splitting between
12+
/// a function or function type's return type and the rest of the function.
1213
///
1314
/// Typed and untyped variables have slightly different splitting logic.
1415
/// Untyped variables never split after the keyword but do indent subsequent
@@ -60,7 +61,7 @@ class VariablePiece extends Piece {
6061

6162
@override
6263
void format(CodeWriter writer, State state) {
63-
writer.format(_header);
64+
writer.format(_header, allowNewlines: state != State.unsplit);
6465

6566
// If we split at the variables (but not the type), then indent the
6667
// variables and their initializers.

0 commit comments

Comments
 (0)