-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Re-land #52859: Revamp the engine style guide, remove always_specify_types
#53223
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,29 +14,182 @@ contributing guide. | |
|
||
## Style | ||
|
||
The Flutter engine follows Google style for the languages it uses: | ||
|
||
- [C++](https://google.github.io/styleguide/cppguide.html) | ||
- **Note**: The Linux embedding generally follows idiomatic GObject-based C | ||
style. Use of C++ is discouraged in that embedding to avoid creating hybrid | ||
code that feels unfamiliar to either developers used to working with | ||
`GObject` or C++ developers. For example, do not use STL collections or | ||
`std::string`. Exceptions: | ||
- C-style casts are forbidden; use C++ casts. | ||
- Use `nullptr` rather than `NULL`. | ||
- Avoid `#define`; for internal constants use `static constexpr` instead. | ||
- [Objective-C][objc_style] (including [Objective-C++][objcc_style]) | ||
- [Java][java_style] | ||
|
||
C/C++ and Objective-C/C++ files are formatted with `clang-format`, and GN files | ||
with `gn format`. | ||
The Flutter engine _generally_ follows Google style for the languages it uses, | ||
with some exceptions. | ||
|
||
### C/C++ | ||
|
||
Follows the [Google C++ Style Guide][google_cpp_style] and is automatically | ||
formatted with `clang-format`. | ||
|
||
Some additional considerations that are in compliance with the style guide, but | ||
are worth noting: | ||
|
||
#### Judiciously use shared_ptr | ||
|
||
The engine currently (as of 2024-05-15) uses `shared_ptr` liberally, which can | ||
be expensive to copy, and is not always necessary. | ||
|
||
The C++ style guide has a | ||
[section on ownership and smart pointers][cpp_ownership] worth reading: | ||
|
||
> Do not design your code to use shared ownership without a very good reason. | ||
> One such reason is to avoid expensive copy operations, but you should only do | ||
> this if the performance benefits are significant, and the underlying object is | ||
> immutable. | ||
|
||
Prefer using `std::unique_ptr` when possible. | ||
|
||
#### Judiciously use `auto` | ||
|
||
The C++ style guide has a [section on type deduction][cpp_auto] that is worth | ||
reading: | ||
|
||
> The fundamental rule is: use type deduction only to make the code clearer or | ||
> safer, and do not use it merely to avoid the inconvenience of writing an | ||
> explicit type. When judging whether the code is clearer, keep in mind that | ||
> your readers are not necessarily on your team, or familiar with your project, | ||
> so types that you and your reviewer experience as unnecessary clutter will | ||
> very often provide useful information to others. For example, you can assume | ||
> that the return type of `make_unique<Foo>()` is obvious, but the return type | ||
> of `MyWidgetFactory()` probably isn't. | ||
|
||
Due to our codebase's extensive use of `shared_ptr`, `auto` can have surprising | ||
performance implications. See [#49801][pr_49801] for an example. | ||
|
||
#### Linux Embedding | ||
|
||
> [!NOTE] | ||
> The Linux embedding instead follows idiomatic GObject-based C style. | ||
|
||
Use of C++ in the [Linux embedding][] is discouraged in that embedding to avoid | ||
creating hybrid code that feels unfamiliar to either developers used to working | ||
with `GObject` or C++ developers. | ||
|
||
For example, _do not_ use STL collections or `std::string`, but _do_: | ||
|
||
- Use C++ casts (C-style casts are forbidden). | ||
- Use `nullptr` rather than `NULL`. | ||
- Avoid `#define`; for internal constants use `static constexpr` instead. | ||
|
||
### Dart | ||
|
||
The Flutter engine _intends_ to follow the [Dart style guide][dart_style] but | ||
currently follows the [Flutter style guide][flutter_style], with the following | ||
exceptions: | ||
|
||
#### Use of type inference is allowed | ||
|
||
The [Dart style guide][dart_inference] only requires explicit types when type | ||
inference is not possible, but the Flutter style guide always requires explicit | ||
types. The engine is moving towards the Dart style guide, but this is a gradual | ||
process. In the meantime, follow these guidelines: | ||
|
||
- **Always** annotate when inference is not possible. | ||
- **Prefer** annotating when inference is possible but the type is not | ||
obvious. | ||
|
||
Some cases when using `var`/`final`/`const` is appropriate: | ||
|
||
- When the type is obvious from the right-hand side of the assignment: | ||
|
||
```dart | ||
// Capitalized constructor name always returns a Foo. | ||
var foo = Foo(); | ||
|
||
// Similar with factory constructors. | ||
var bar = Bar.create(); | ||
|
||
// Literals (strings, numbers, lists, maps, etc) always return the same type. | ||
var name = 'John Doe'; | ||
var flag = true; | ||
var numbers = [1, 2, 3]; | ||
var map = {'one': 1, 'two': 2, 'three': 3}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a similar example. Consider |
||
``` | ||
|
||
- When the type is obvious from the method name: | ||
|
||
```dart | ||
// toString() always returns a String. | ||
var string = foo().toString(); | ||
|
||
// It's reasonable to assume that length returns an int. | ||
var length = string.length; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whether it's reasonable or not seems pretty subjective. (Also, it's not actually reasonable for "String" to have a "length" member in the first place... what should it return? It's almost never what you want. |
||
``` | ||
|
||
- When the type is obvious from the context: | ||
|
||
```dart | ||
// When variables are in the same scope, reduce() clearly returns an int. | ||
var list = [1, 2, 3]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as noted above, the type of this literal is not obvious. |
||
var sum = list.reduce((a, b) => a + b); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this example, I think @stuartmorgan had feedback at the meeting that the judgment of how much context is needed should be made on individual lines in isolation. That way, if in a subsequent change, the lines of code end up getting inserted between these two lines, we don't end up with a line who is too separated from that context. I think that's a pretty reasonable guideline personally, so I would be in favor of changing this example and perhaps even explicitly mentioning the rule of thumb Stuart suggested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can rely on reviewers and drive-by code improvements to enforce these more subtle issues? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah to be clear, I hear the issue, I think it's worth thinking about. I don't think there is a specific line count I can suggest, some folks will think 10 lines are OK, others will balk at 4 lines. Let's try this as-is, and if there are real issues that result of not having stricter guidance we can revisit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I double-checked the C++ guide later, and I was wrong about |
||
``` | ||
|
||
Some cases where an explicit type should be considered: | ||
|
||
- When the type is not obvious from the right-hand side of the assignment: | ||
|
||
```dart | ||
// What does 'fetchLatest()' return? | ||
ImageBuffer buffer = fetchLatest(); | ||
|
||
// What does this large chain of method calls return? | ||
Iterable<int> numbers = foo().bar().map((b) => b.baz()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for these in general I would recommend putting the types on the method calls, so that the type checking happens earlier in the process (e.g. using |
||
``` | ||
|
||
- When there are semantic implications to the type: | ||
|
||
```dart | ||
// Without 'num', the map would be inferred as 'Map<String, int>'. | ||
const Map<String, num> map = {'one': 1, 'two': 2, 'three': 3}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you mentioned you wanted to move the types to the right hand side here in this example? i.e.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, ezpz. |
||
``` | ||
|
||
- Or, **when a reviewer requests it**! | ||
|
||
Remember that the goal is to make the code more readable and maintainable, and | ||
explicit types _can_ help with that. Code can be changed, so it's always | ||
possible to add or remove type annotations later as the code evolves, so avoid | ||
bikeshedding over this. | ||
|
||
### Java | ||
|
||
Follows the [Google Java Style Guide][java_style] and is automatically formatted | ||
with `google-java-format`. | ||
|
||
### Objective-C | ||
|
||
Follows the [Google Objective-C Style Guide][objc_style], including for | ||
Objective-C++ and is automatically formatted with `clang-format`. | ||
|
||
### Python | ||
|
||
Follows the [Google Python Style Guide][google_python_style] and is | ||
automatically formatted with `yapf`. | ||
|
||
> [!WARNING] | ||
> Historically, the engine grew a number of one-off Python scripts, often as | ||
> part of the testing or build infrastructure (i.e. command-line tools). We are | ||
> instead moving towards using Dart for these tasks, so new Python scripts | ||
> should be avoided whenever possible. | ||
|
||
### GN | ||
|
||
Automatically formatted with `gn format`. | ||
|
||
[cpp_auto]: https://google.github.io/styleguide/cppguide.html#Type_deduction | ||
[cpp_ownership]: https://google.github.io/styleguide/cppguide.html#Ownership_and_Smart_Pointers | ||
[dart_inference]: https://dart.dev/effective-dart/design#types | ||
[dart_style]: https://dart.dev/effective-dart/style | ||
[linux embedding]: shell/platform/linux | ||
[google_cpp_style]: https://google.github.io/styleguide/cppguide.html | ||
[pr_49801]: https://github.com/flutter/engine/pull/49801 | ||
[code_of_conduct]: https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md | ||
[contrib_guide]: https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md | ||
[engine_dev_setup]: https://github.com/flutter/flutter/blob/master/docs/engine/contributing/Setting-up-the-Engine-development-environment.md | ||
[flutter_style]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo | ||
[objc_style]: https://google.github.io/styleguide/objcguide.html | ||
[objcc_style]: https://google.github.io/styleguide/objcguide.html#objective-c | ||
[java_style]: https://google.github.io/styleguide/javaguide.html | ||
[google_python_style]: https://google.github.io/styleguide/pyguide.html | ||
|
||
## Testing | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this PR was posted and landed while I was out of town so I didn't have the opportunity to comment on it, so apologies for commenting after it landed)
This particular case (
[1, 2, 3]
) is one where having types explicitly somewhere (whether lhs or rhs doesn't matter) is actually really important. Consider for example an array[1.1]
where one day the1.1
is changed to1
. Despite this looking like an innocent change, it will change the inferred type.In general, the int and double literals are not uniquely typed, and so I would always specify the types when number literals are used.
It's especially important because there's actually three types. For example,
[1.1, 2]
is not aList<double>
, it's aList<num>
. It's very easy to end up in situations where the exact type is not actually what it seems to "obviously" be, especially when code is being edited (as opposed to first written).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there real-world cases where that change in inferred type both matters, but isn't caught by the compiler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the most common places for array literals is tests, where often the literals are then compared in type-unsafe ways (e.g. using
expect
). Not only would the type be not what the developer expects, and not only would the compiler not notice, but the test might even still pass despite the type being "wrong". This can lead to different people reading the code getting different interpretations of what the code intended (especially after multiple people have edited the code), which is exactly the thing we're trying to avoid by having a consistent style.Explicit types everywhere avoids that in a verbose and brute-force manner. The more nuanced style suggestions here are much less verbose and lead to leaner code, but at the cost of requiring more thought when deciding where to include types or not include types. One example of that is this int/num/double confusion.