Skip to content

Commit 5d7445e

Browse files
stereotype441Commit Bot
authored and
Commit Bot
committed
Migration: respect built_value @nullable annotations even when there is no codegen
Previously, when the migration tool encountered a built_value `@nullable` annotation, it removed it, because when the built_value code generator is consuming a library with null safety enabled, it relies on the presence of a `?` to determine nullability, rather than an annotation. However, there was no logic to actually ensure that the `?` would actually get introduced, because I thought the code generated by built_value would always create the conditions necessary to convince the migration tool to introduce the `?` using its normal graph traversal algorithm. It turns out this is not the case: when `@nullable` appears in an interface class that is used by other built_value classes, but is not itself a built_value class, the migration tool sometimes doesn't have enough information to figure out that it needs to add the `?`. So in this CL, I'm doing what I probably should have done in the first time: adding the necessary logic to the migration tool to ensure that the `@nullable` annotation gets translated into a `?` regardless of whether it is required to by generated code. Bug: https://buganizer.corp.google.com/issues/217863427 Change-Id: I9efe5241634389981a4c56e764bac91b3350c4fb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/233003 Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent a2f887e commit 5d7445e

File tree

6 files changed

+77
-19
lines changed

6 files changed

+77
-19
lines changed

pkg/nnbd_migration/lib/instrumentation.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ enum EdgeOriginKind {
242242
alwaysNullableType,
243243
angularAnnotation,
244244
argumentErrorCheckNotNull,
245+
builtValueNullableAnnotation,
245246
callTearOff,
246247
compoundAssignment,
247248
// See [DummyOrigin].

pkg/nnbd_migration/lib/src/edge_builder.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import 'package:nnbd_migration/src/edge_origin.dart';
2929
import 'package:nnbd_migration/src/expression_checks.dart';
3030
import 'package:nnbd_migration/src/nullability_node.dart';
3131
import 'package:nnbd_migration/src/nullability_node_target.dart';
32+
import 'package:nnbd_migration/src/utilities/built_value_transformer.dart';
3233
import 'package:nnbd_migration/src/utilities/completeness_tracker.dart';
3334
import 'package:nnbd_migration/src/utilities/hint_utils.dart';
3435
import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
@@ -1330,6 +1331,14 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType>
13301331

13311332
@override
13321333
DecoratedType? visitMethodDeclaration(MethodDeclaration node) {
1334+
if (BuiltValueTransformer.findNullableAnnotation(node) != null) {
1335+
_graph.makeNullable(
1336+
_variables!
1337+
.decoratedElementType(node.declaredElement!.declaration)
1338+
.returnType!
1339+
.node!,
1340+
BuiltValueNullableOrigin(source, node));
1341+
}
13331342
_handleExecutableDeclaration(node, node.declaredElement!, node.metadata,
13341343
node.returnType, node.parameters, null, node.body, null);
13351344
_dispatch(node.typeParameters);

pkg/nnbd_migration/lib/src/edge_origin.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ class ArgumentErrorCheckNotNullOrigin extends EdgeOrigin {
9898
EdgeOriginKind get kind => EdgeOriginKind.argumentErrorCheckNotNull;
9999
}
100100

101+
/// Edge origin resulting from a use of built_value's `@nullable` annotation.
102+
class BuiltValueNullableOrigin extends EdgeOrigin {
103+
BuiltValueNullableOrigin(Source? source, AstNode node) : super(source, node);
104+
105+
@override
106+
String get description => 'method is marked with the `@nullable` annotation';
107+
108+
@override
109+
EdgeOriginKind get kind => EdgeOriginKind.builtValueNullableAnnotation;
110+
}
111+
101112
/// An edge origin used for edges that originated because of a tear-off of
102113
/// `call` on a function type.
103114
class CallTearOffOrigin extends EdgeOrigin {

pkg/nnbd_migration/lib/src/fix_builder.dart

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import 'package:nnbd_migration/src/decorated_type.dart';
3535
import 'package:nnbd_migration/src/edit_plan.dart';
3636
import 'package:nnbd_migration/src/fix_aggregator.dart';
3737
import 'package:nnbd_migration/src/nullability_node.dart';
38+
import 'package:nnbd_migration/src/utilities/built_value_transformer.dart';
3839
import 'package:nnbd_migration/src/utilities/permissive_mode.dart';
3940
import 'package:nnbd_migration/src/utilities/resolution_utils.dart';
4041
import 'package:nnbd_migration/src/utilities/where_or_null_transformer.dart';
@@ -1245,25 +1246,13 @@ class _FixBuilderPreVisitor extends GeneralizingAstVisitor<void>
12451246

12461247
@override
12471248
void visitMethodDeclaration(MethodDeclaration node) {
1248-
if (node.isGetter && node.isAbstract) {
1249-
for (var annotation in node.metadata) {
1250-
if (annotation.arguments == null) {
1251-
var element = annotation.element;
1252-
if (element is PropertyAccessorElement &&
1253-
element.name == 'nullable') {
1254-
if (element.enclosingElement is CompilationUnitElement) {
1255-
if (element.library.source.uri.toString() ==
1256-
'package:built_value/built_value.dart') {
1257-
var info = AtomicEditInfo(
1258-
NullabilityFixDescription.removeNullableAnnotation, {});
1259-
(_fixBuilder._getChange(node) as NodeChangeForMethodDeclaration)
1260-
..annotationToRemove = annotation
1261-
..removeAnnotationInfo = info;
1262-
}
1263-
}
1264-
}
1265-
}
1266-
}
1249+
var nullableAnnotation = BuiltValueTransformer.findNullableAnnotation(node);
1250+
if (nullableAnnotation != null) {
1251+
var info = AtomicEditInfo(
1252+
NullabilityFixDescription.removeNullableAnnotation, {});
1253+
(_fixBuilder._getChange(node) as NodeChangeForMethodDeclaration)
1254+
..annotationToRemove = nullableAnnotation
1255+
..removeAnnotationInfo = info;
12671256
}
12681257
super.visitMethodDeclaration(node);
12691258
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/element/element.dart';
7+
8+
class BuiltValueTransformer {
9+
static Annotation? findNullableAnnotation(MethodDeclaration node) {
10+
if (node.isGetter && node.isAbstract) {
11+
for (var annotation in node.metadata) {
12+
if (annotation.arguments == null) {
13+
var element = annotation.element;
14+
if (element is PropertyAccessorElement &&
15+
element.name == 'nullable') {
16+
if (element.enclosingElement is CompilationUnitElement) {
17+
if (element.library.source.uri.toString() ==
18+
'package:built_value/built_value.dart') {
19+
return annotation;
20+
}
21+
}
22+
}
23+
}
24+
}
25+
}
26+
return null;
27+
}
28+
}

pkg/nnbd_migration/test/api_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,26 @@ class FooBuilder implements Builder<Foo, FooBuilder> {
977977
{path1: file1, path2: file2}, {path1: expected1, path2: anything});
978978
}
979979

980+
Future<void> test_built_value_nullable_getter_interface_only() async {
981+
addBuiltValuePackage();
982+
var content = '''
983+
import 'package:built_value/built_value.dart';
984+
985+
abstract class Foo {
986+
@nullable
987+
int get value;
988+
}
989+
''';
990+
var expected = '''
991+
import 'package:built_value/built_value.dart';
992+
993+
abstract class Foo {
994+
int? get value;
995+
}
996+
''';
997+
await _checkSingleFileChanges(content, expected);
998+
}
999+
9801000
Future<void> test_call_already_migrated_extension() async {
9811001
var content = '''
9821002
import 'already_migrated.dart';

0 commit comments

Comments
 (0)