Version 2.14.0-62.0.dev
Merge commit 'a89fccb482cb5d263ebbe0191dbd201f7af2603a' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
index 5a8d94b..317b6a8 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/perform_refactor.dart
@@ -164,6 +164,11 @@
InlineLocalRefactoring(server.searchEngine, result, offset);
return success(refactor);
+ case RefactoringKind.INLINE_METHOD:
+ final refactor =
+ InlineMethodRefactoring(server.searchEngine, result, offset);
+ return success(refactor);
+
default:
return error(ServerErrorCodes.InvalidCommandArguments,
'Unknown RefactoringKind $kind was supplied to $commandName');
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 2d786af..322e9d8d 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -409,6 +409,7 @@
}
}
+ // Inlines
if (shouldIncludeKind(CodeActionKind.RefactorInline)) {
// Inline Local Variable
if (InlineLocalRefactoring(server.searchEngine, unit, offset)
@@ -416,6 +417,13 @@
refactorActions.add(createRefactor(CodeActionKind.RefactorInline,
'Inline Local Variable', RefactoringKind.INLINE_LOCAL_VARIABLE));
}
+
+ // Inline Method
+ if (InlineMethodRefactoring(server.searchEngine, unit, offset)
+ .isAvailable()) {
+ refactorActions.add(createRefactor(CodeActionKind.RefactorInline,
+ 'Inline Method', RefactoringKind.INLINE_METHOD));
+ }
}
return refactorActions;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
index 47362ca..1cff243 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/inline_method.dart
@@ -290,6 +290,39 @@
return Future.value(change);
}
+ @override
+ bool isAvailable() {
+ return !_checkOffset().hasFatalError;
+ }
+
+ /// Checks if [offset] is a method that can be inlined.
+ RefactoringStatus _checkOffset() {
+ var fatalStatus = RefactoringStatus.fatal(
+ 'Method declaration or reference must be selected to activate this refactoring.');
+
+ var identifier = NodeLocator(offset).searchWithin(resolveResult.unit);
+ if (identifier is! SimpleIdentifier) {
+ return fatalStatus;
+ }
+ var element = identifier.writeOrReadElement;
+ if (element is! ExecutableElement) {
+ return fatalStatus;
+ }
+ if (element.isSynthetic) {
+ return fatalStatus;
+ }
+ // maybe operator
+ if (element.isOperator) {
+ return RefactoringStatus.fatal('Cannot inline operator.');
+ }
+ // maybe [a]sync*
+ if (element.isGenerator) {
+ return RefactoringStatus.fatal('Cannot inline a generator.');
+ }
+
+ return RefactoringStatus();
+ }
+
_SourcePart _createSourcePart(SourceRange range) {
var source = _methodUtils.getRangeText(range);
var prefix = getLinePrefix(source);
diff --git a/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart b/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
index 23b8552..cdb3485 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/refactoring.dart
@@ -242,6 +242,10 @@
/// The name of the method (or function) being inlined.
String? get methodName;
+
+ /// Return `true` if refactoring is available, possibly without checking all
+ /// initial conditions.
+ bool isAvailable();
}
/// [Refactoring] to move/rename a file.
diff --git a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
index 8d87a88..791c627d 100644
--- a/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_refactor_test.dart
@@ -19,6 +19,7 @@
defineReflectiveTests(ExtractWidgetRefactorCodeActionsTest);
defineReflectiveTests(ExtractVariableRefactorCodeActionsTest);
defineReflectiveTests(InlineLocalVariableRefactorCodeActionsTest);
+ defineReflectiveTests(InlineMethodRefactorCodeActionsTest);
});
}
@@ -515,7 +516,7 @@
@reflectiveTest
class InlineLocalVariableRefactorCodeActionsTest
extends AbstractCodeActionsTest {
- final extractVariableTitle = 'Inline Local Variable';
+ final inlineVariableTitle = 'Inline Local Variable';
Future<void> test_appliesCorrectEdits() async {
const content = '''
@@ -539,7 +540,86 @@
final codeActions = await getCodeActions(mainFileUri.toString(),
position: positionFromMarker(content));
final codeAction = findCommand(
- codeActions, Commands.performRefactor, extractVariableTitle)!;
+ codeActions, Commands.performRefactor, inlineVariableTitle)!;
+
+ await verifyCodeActionEdits(
+ codeAction, withoutMarkers(content), expectedContent);
+ }
+}
+
+@reflectiveTest
+class InlineMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
+ final inlineMethodTitle = 'Inline Method';
+
+ Future<void> test_inlineAtCallSite() async {
+ const content = '''
+void foo1() {
+ ba^r();
+}
+
+void foo2() {
+ bar();
+}
+
+void bar() {
+ print('test');
+}
+ ''';
+ const expectedContent = '''
+void foo1() {
+ print('test');
+}
+
+void foo2() {
+ bar();
+}
+
+void bar() {
+ print('test');
+}
+ ''';
+ newFile(mainFilePath, content: withoutMarkers(content));
+ await initialize();
+
+ final codeActions = await getCodeActions(mainFileUri.toString(),
+ position: positionFromMarker(content));
+ final codeAction =
+ findCommand(codeActions, Commands.performRefactor, inlineMethodTitle)!;
+
+ await verifyCodeActionEdits(
+ codeAction, withoutMarkers(content), expectedContent);
+ }
+
+ Future<void> test_inlineAtMethod() async {
+ const content = '''
+void foo1() {
+ bar();
+}
+
+void foo2() {
+ bar();
+}
+
+void ba^r() {
+ print('test');
+}
+ ''';
+ const expectedContent = '''
+void foo1() {
+ print('test');
+}
+
+void foo2() {
+ print('test');
+}
+ ''';
+ newFile(mainFilePath, content: withoutMarkers(content));
+ await initialize();
+
+ final codeActions = await getCodeActions(mainFileUri.toString(),
+ position: positionFromMarker(content));
+ final codeAction =
+ findCommand(codeActions, Commands.performRefactor, inlineMethodTitle)!;
await verifyCodeActionEdits(
codeAction, withoutMarkers(content), expectedContent);
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index c4fe216..d200491 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -3056,7 +3056,21 @@
if (value is AbortConstant) return value;
env.addVariableValue(parameter, value);
}
- return executeBody(function.body);
+
+ final Constant result = executeBody(function.body);
+ if (result is NullConstant &&
+ function.returnType.nullability == Nullability.nonNullable) {
+ // Ensure that the evaluated constant returned is not null if the
+ // function has a non-nullable return type.
+ return createErrorConstant(
+ function,
+ templateConstEvalInvalidType.withArguments(
+ result,
+ function.returnType,
+ result.getType(_staticTypeContext),
+ isNonNullableByDefault));
+ }
+ return result;
}
if (functionEnvironment != null) {
@@ -3822,6 +3836,8 @@
if (node.initializer != null) {
value = evaluate(node.initializer);
if (value is AbortConstant) return new AbortStatus(value);
+ } else {
+ value = new NullConstant();
}
exprEvaluator.env.addVariableValue(node, value);
return const ProceedStatus();
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart
index 20071a4..7f6d6fe 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart
@@ -62,6 +62,12 @@
return a;
}
+const var9 = function9();
+int? function9() {
+ int? x;
+ return x;
+}
+
void main() {
Expect.equals(var1, 4);
Expect.equals(var1_1, 5);
@@ -72,4 +78,5 @@
Expect.equals(var6, 2);
Expect.equals(var7, 2);
Expect.equals(var8, 2);
+ Expect.equals(var9, null);
}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.expect
index e424e71..d270f74 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.expect
@@ -14,6 +14,7 @@
static const field core::int var6 = #C5;
static const field core::int var7 = #C5;
static const field core::int var8 = #C5;
+static const field core::int? var9 = #C7;
static method function1(core::int a, core::int b) → core::int {
core::int x = 1.{core::num::+}(a).{core::num::+}(b);
return x;
@@ -52,6 +53,10 @@
a = 2;
return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
}
+static method function9() → core::int? {
+ core::int? x;
+ return x;
+}
static method main() → void {
exp::Expect::equals(#C1, 4);
exp::Expect::equals(#C2, 5);
@@ -62,6 +67,7 @@
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
+ exp::Expect::equals(#C7, null);
}
constants {
@@ -71,4 +77,5 @@
#C4 = 6
#C5 = 2
#C6 = -2
+ #C7 = null
}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.transformed.expect
index 611523b..3ec97cd 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.strong.transformed.expect
@@ -14,6 +14,7 @@
static const field core::int var6 = #C5;
static const field core::int var7 = #C5;
static const field core::int var8 = #C5;
+static const field core::int? var9 = #C7;
static method function1(core::int a, core::int b) → core::int {
core::int x = 1.{core::num::+}(a).{core::num::+}(b);
return x;
@@ -52,6 +53,10 @@
a = 2;
return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
}
+static method function9() → core::int? {
+ core::int? x;
+ return x;
+}
static method main() → void {
exp::Expect::equals(#C1, 4);
exp::Expect::equals(#C2, 5);
@@ -62,6 +67,7 @@
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
+ exp::Expect::equals(#C7, null);
}
constants {
@@ -71,8 +77,9 @@
#C4 = 6
#C5 = 2
#C6 = -2
+ #C7 = null
}
Extra constant evaluation status:
-Evaluated: MethodInvocation @ org-dartlang-testcase:///const_functions_variable_declarations.dart:71:23 -> IntConstant(-2)
-Extra constant evaluation: evaluated: 33, effectively constant: 1
+Evaluated: MethodInvocation @ org-dartlang-testcase:///const_functions_variable_declarations.dart:77:23 -> IntConstant(-2)
+Extra constant evaluation: evaluated: 35, effectively constant: 1
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline.expect
index 1c817fd..2a0d7a9 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline.expect
@@ -17,4 +17,6 @@
int function7() {}
const var8 = function8();
int function8() {}
+const var9 = function9();
+int? function9() {}
void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline_modelled.expect
index 2d98733..519ab22 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline_modelled.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.textual_outline_modelled.expect
@@ -10,6 +10,8 @@
const var6 = function6();
const var7 = function7();
const var8 = function8();
+const var9 = function9();
+int? function9() {}
int function1(int a, int b) {}
int function3() {}
int function4() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.expect
index e424e71..d270f74 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.expect
@@ -14,6 +14,7 @@
static const field core::int var6 = #C5;
static const field core::int var7 = #C5;
static const field core::int var8 = #C5;
+static const field core::int? var9 = #C7;
static method function1(core::int a, core::int b) → core::int {
core::int x = 1.{core::num::+}(a).{core::num::+}(b);
return x;
@@ -52,6 +53,10 @@
a = 2;
return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
}
+static method function9() → core::int? {
+ core::int? x;
+ return x;
+}
static method main() → void {
exp::Expect::equals(#C1, 4);
exp::Expect::equals(#C2, 5);
@@ -62,6 +67,7 @@
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
+ exp::Expect::equals(#C7, null);
}
constants {
@@ -71,4 +77,5 @@
#C4 = 6
#C5 = 2
#C6 = -2
+ #C7 = null
}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.outline.expect
index 50fbc0f..09275b2 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.outline.expect
@@ -13,6 +13,7 @@
static const field core::int var6 = self::function6();
static const field core::int var7 = self::function7();
static const field core::int var8 = self::function8();
+static const field core::int? var9 = self::function9();
static method function1(core::int a, core::int b) → core::int
;
static method function2() → core::String
@@ -29,5 +30,7 @@
;
static method function8() → core::int
;
+static method function9() → core::int?
+ ;
static method main() → void
;
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.transformed.expect
index 611523b..3ec97cd 100644
--- a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations.dart.weak.transformed.expect
@@ -14,6 +14,7 @@
static const field core::int var6 = #C5;
static const field core::int var7 = #C5;
static const field core::int var8 = #C5;
+static const field core::int? var9 = #C7;
static method function1(core::int a, core::int b) → core::int {
core::int x = 1.{core::num::+}(a).{core::num::+}(b);
return x;
@@ -52,6 +53,10 @@
a = 2;
return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
}
+static method function9() → core::int? {
+ core::int? x;
+ return x;
+}
static method main() → void {
exp::Expect::equals(#C1, 4);
exp::Expect::equals(#C2, 5);
@@ -62,6 +67,7 @@
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
exp::Expect::equals(#C5, 2);
+ exp::Expect::equals(#C7, null);
}
constants {
@@ -71,8 +77,9 @@
#C4 = 6
#C5 = 2
#C6 = -2
+ #C7 = null
}
Extra constant evaluation status:
-Evaluated: MethodInvocation @ org-dartlang-testcase:///const_functions_variable_declarations.dart:71:23 -> IntConstant(-2)
-Extra constant evaluation: evaluated: 33, effectively constant: 1
+Evaluated: MethodInvocation @ org-dartlang-testcase:///const_functions_variable_declarations.dart:77:23 -> IntConstant(-2)
+Extra constant evaluation: evaluated: 35, effectively constant: 1
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart
new file mode 100644
index 0000000..e8b7643
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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.
+
+// Tests erroneous variable declaration usage within const functions.
+
+import "package:expect/expect.dart";
+
+const var1 = fn1();
+int fn1() {
+ var a;
+ return a;
+}
+
+const var2 = fn2();
+int fn2() {
+ var x;
+ x = "string";
+ return x;
+}
+
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.expect
new file mode 100644
index 0000000..3943426
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:14: Error: Constant evaluation error:
+// const var1 = fn1();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:12:10: Context: Expected constant 'null' to be of type 'int', but was of type 'Null'.
+// return a;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:7: Context: While analyzing:
+// const var1 = fn1();
+// ^
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:14: Error: Constant evaluation error:
+// const var2 = fn2();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:19:10: Context: Expected constant '"string"' to be of type 'int', but was of type 'String'.
+// return x;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:7: Context: While analyzing:
+// const var2 = fn2();
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = invalid-expression "Expected constant 'null' to be of type 'int', but was of type 'Null'.";
+static const field core::int var2 = invalid-expression "Expected constant '\"string\"' to be of type 'int', but was of type 'String'.";
+static method fn1() → core::int {
+ dynamic a;
+ return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method fn2() → core::int {
+ dynamic x;
+ x = "string";
+ return x as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.transformed.expect
new file mode 100644
index 0000000..3943426
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.strong.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:14: Error: Constant evaluation error:
+// const var1 = fn1();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:12:10: Context: Expected constant 'null' to be of type 'int', but was of type 'Null'.
+// return a;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:7: Context: While analyzing:
+// const var1 = fn1();
+// ^
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:14: Error: Constant evaluation error:
+// const var2 = fn2();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:19:10: Context: Expected constant '"string"' to be of type 'int', but was of type 'String'.
+// return x;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:7: Context: While analyzing:
+// const var2 = fn2();
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = invalid-expression "Expected constant 'null' to be of type 'int', but was of type 'Null'.";
+static const field core::int var2 = invalid-expression "Expected constant '\"string\"' to be of type 'int', but was of type 'String'.";
+static method fn1() → core::int {
+ dynamic a;
+ return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method fn2() → core::int {
+ dynamic x;
+ x = "string";
+ return x as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline.expect
new file mode 100644
index 0000000..422a5ed
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+import "package:expect/expect.dart";
+
+const var1 = fn1();
+int fn1() {}
+const var2 = fn2();
+int fn2() {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..d67a0d9
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+import "package:expect/expect.dart";
+
+const var1 = fn1();
+const var2 = fn2();
+int fn1() {}
+int fn2() {}
+void main() {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.expect
new file mode 100644
index 0000000..9d6fa65
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:14: Error: Constant evaluation error:
+// const var1 = fn1();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:10:8: Context: Expected constant 'null' to be of type 'int', but was of type 'Null'.
+// int fn1() {
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:7: Context: While analyzing:
+// const var1 = fn1();
+// ^
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:14: Error: Constant evaluation error:
+// const var2 = fn2();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:19:10: Context: Expected constant '"string"' to be of type 'int', but was of type 'String'.
+// return x;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:7: Context: While analyzing:
+// const var2 = fn2();
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = invalid-expression "Expected constant 'null' to be of type 'int', but was of type 'Null'.";
+static const field core::int var2 = invalid-expression "Expected constant '\"string\"' to be of type 'int', but was of type 'String'.";
+static method fn1() → core::int {
+ dynamic a;
+ return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method fn2() → core::int {
+ dynamic x;
+ x = "string";
+ return x as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method main() → void {}
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.outline.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.outline.expect
new file mode 100644
index 0000000..68a781d
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.outline.expect
@@ -0,0 +1,14 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = self::fn1();
+static const field core::int var2 = self::fn2();
+static method fn1() → core::int
+ ;
+static method fn2() → core::int
+ ;
+static method main() → void
+ ;
diff --git a/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.transformed.expect b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.transformed.expect
new file mode 100644
index 0000000..9d6fa65
--- /dev/null
+++ b/pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart.weak.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:14: Error: Constant evaluation error:
+// const var1 = fn1();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:10:8: Context: Expected constant 'null' to be of type 'int', but was of type 'Null'.
+// int fn1() {
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:9:7: Context: While analyzing:
+// const var1 = fn1();
+// ^
+//
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:14: Error: Constant evaluation error:
+// const var2 = fn2();
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:19:10: Context: Expected constant '"string"' to be of type 'int', but was of type 'String'.
+// return x;
+// ^
+// pkg/front_end/testcases/const_functions/const_functions_variable_declarations_error.dart:15:7: Context: While analyzing:
+// const var2 = fn2();
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+import "package:expect/expect.dart";
+
+static const field core::int var1 = invalid-expression "Expected constant 'null' to be of type 'int', but was of type 'Null'.";
+static const field core::int var2 = invalid-expression "Expected constant '\"string\"' to be of type 'int', but was of type 'String'.";
+static method fn1() → core::int {
+ dynamic a;
+ return a as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method fn2() → core::int {
+ dynamic x;
+ x = "string";
+ return x as{TypeError,ForDynamic,ForNonNullableByDefault} core::int;
+}
+static method main() → void {}
diff --git a/runtime/vm/compiler/relocation.cc b/runtime/vm/compiler/relocation.cc
index e2f44aa..428a84f 100644
--- a/runtime/vm/compiler/relocation.cc
+++ b/runtime/vm/compiler/relocation.cc
@@ -81,13 +81,8 @@
auto& current_caller = Code::Handle(zone);
auto& call_targets = Array::Handle(zone);
- // Do one linear pass over all code objects and determine:
- //
- // * the maximum instruction size
- // * the maximum number of calls
- // * the maximum offset into a target instruction
- //
- FindLargestInstruction();
+ auto& next_caller = Code::Handle(zone);
+ auto& next_caller_targets = Array::Handle(zone);
// Emit all instructions and do relocations on the way.
for (intptr_t i = 0; i < code_objects_->length(); ++i) {
@@ -106,8 +101,14 @@
// If we have forward/backwards calls which are almost out-of-range, we'll
// create trampolines now.
- BuildTrampolinesForAlmostOutOfRangeCalls(
- /*force=*/(i == (code_objects_->length() - 1)));
+ if (i < (code_objects_->length() - 1)) {
+ next_caller = (*code_objects_)[i + 1];
+ next_caller_targets = next_caller.static_calls_target_table();
+ } else {
+ next_caller = Code::null();
+ next_caller_targets = Array::null();
+ }
+ BuildTrampolinesForAlmostOutOfRangeCalls(next_caller, next_caller_targets);
}
// We're guaranteed to have all calls resolved, since
@@ -143,40 +144,6 @@
// however we might need it to write information into V8 snapshot profile.
}
-void CodeRelocator::FindLargestInstruction() {
- auto zone = thread_->zone();
- auto& current_caller = Code::Handle(zone);
- auto& call_targets = Array::Handle(zone);
-
- for (intptr_t i = 0; i < code_objects_->length(); ++i) {
- current_caller = (*code_objects_)[i];
- const intptr_t size =
- ImageWriter::SizeInSnapshot(current_caller.instructions());
- if (size > max_instructions_size_) {
- max_instructions_size_ = size;
- }
-
- call_targets = current_caller.static_calls_target_table();
- if (!call_targets.IsNull()) {
- intptr_t num_calls = 0;
- StaticCallsTable calls(call_targets);
- for (auto call : calls) {
- kind_type_and_offset_ = call.Get<Code::kSCallTableKindAndOffset>();
- const auto kind =
- Code::KindField::decode(kind_type_and_offset_.Value());
- if (kind == Code::kCallViaCode) {
- continue;
- }
- num_calls++;
- }
-
- if (num_calls > max_calls_) {
- max_calls_ = num_calls;
- }
- }
- }
-}
-
bool CodeRelocator::AddInstructionsToText(CodePtr code) {
InstructionsPtr instructions = Code::InstructionsOf(code);
@@ -418,11 +385,11 @@
const auto forward_distance =
target_text_offset - unresolved_call->text_offset;
if (unresolved_call->is_tail_call) {
- return TailCallDistanceLimits::Lower() < forward_distance &&
- forward_distance < TailCallDistanceLimits::Upper();
+ return TailCallDistanceLimits::Lower() <= forward_distance &&
+ forward_distance <= TailCallDistanceLimits::Upper();
} else {
- return CallDistanceLimits::Lower() < forward_distance &&
- forward_distance < CallDistanceLimits::Upper();
+ return CallDistanceLimits::Lower() <= forward_distance &&
+ forward_distance <= CallDistanceLimits::Upper();
}
}
@@ -478,21 +445,37 @@
return destination_.ptr();
}
-void CodeRelocator::BuildTrampolinesForAlmostOutOfRangeCalls(bool force) {
+void CodeRelocator::BuildTrampolinesForAlmostOutOfRangeCalls(
+ const Code& next_caller,
+ const Array& next_caller_targets) {
+ const bool all_functions_emitted = next_caller.IsNull();
+
+ uword next_size = 0;
+ uword next_call_count = 0;
+ if (!all_functions_emitted) {
+ next_size = ImageWriter::SizeInSnapshot(next_caller.instructions());
+ if (!next_caller_targets.IsNull()) {
+ StaticCallsTable calls(next_caller_targets);
+ next_call_count = calls.Length();
+ }
+ }
+
while (!all_unresolved_calls_.IsEmpty()) {
UnresolvedCall* unresolved_call = all_unresolved_calls_.First();
- // If we can emit another instructions object without causing the unresolved
- // forward calls to become out-of-range, we'll not resolve it yet (maybe the
- // target function will come very soon and we don't need a trampoline at
- // all).
- const intptr_t future_boundary =
- next_text_offset_ + max_instructions_size_ +
- kTrampolineSize *
- (unresolved_calls_by_destination_.Length() + max_calls_);
- if (IsTargetInRangeFor(unresolved_call, future_boundary) &&
- !FLAG_always_generate_trampolines_for_testing && !force) {
- break;
+ if (!all_functions_emitted) {
+ // If we can emit another instructions object without causing the
+ // unresolved forward calls to become out-of-range, we'll not resolve it
+ // yet (maybe the target function will come very soon and we don't need
+ // a trampoline at all).
+ const intptr_t future_boundary =
+ next_text_offset_ + next_size +
+ kTrampolineSize *
+ (unresolved_calls_by_destination_.Length() + next_call_count - 1);
+ if (IsTargetInRangeFor(unresolved_call, future_boundary) &&
+ !FLAG_always_generate_trampolines_for_testing) {
+ break;
+ }
}
// We have a "critical" [unresolved_call] we have to resolve. If an
diff --git a/runtime/vm/compiler/relocation.h b/runtime/vm/compiler/relocation.h
index bd9cc15..d3bf294 100644
--- a/runtime/vm/compiler/relocation.h
+++ b/runtime/vm/compiler/relocation.h
@@ -183,7 +183,9 @@
intptr_t destination_text);
void ResolveTrampoline(UnresolvedTrampoline* unresolved_trampoline);
- void BuildTrampolinesForAlmostOutOfRangeCalls(bool force);
+ void BuildTrampolinesForAlmostOutOfRangeCalls(
+ const Code& next_caller,
+ const Array& next_caller_targets);
intptr_t FindDestinationInText(const InstructionsPtr destination,
intptr_t offset_into_target);
diff --git a/runtime/vm/compiler/relocation_test.cc b/runtime/vm/compiler/relocation_test.cc
index 3da5aa7..fa73d3a 100644
--- a/runtime/vm/compiler/relocation_test.cc
+++ b/runtime/vm/compiler/relocation_test.cc
@@ -23,6 +23,20 @@
DECLARE_FLAG(int, upper_pc_relative_call_distance);
struct RelocatorTestHelper {
+ const intptr_t kTrampolineSize =
+ Utils::RoundUp(PcRelativeTrampolineJumpPattern::kLengthInBytes,
+ compiler::target::Instructions::kBarePayloadAlignment);
+
+ // The callers on arm/arm64 have to save LR before calling, so the call
+ // instruction will be 4 byte sinto the instruction stream.
+#if defined(TARGET_ARCH_ARM64)
+ static const intptr_t kOffsetOfCall = 4;
+#elif defined(TARGET_ARCH_ARM)
+ static const intptr_t kOffsetOfCall = 4;
+#else
+ static const intptr_t kOffsetOfCall = 0;
+#endif
+
explicit RelocatorTestHelper(Thread* thread)
: thread(thread),
locker(thread, thread->isolate_group()->program_lock()),
@@ -67,11 +81,6 @@
EmitCodeFor(code, [&](compiler::Assembler* assembler) {
#if defined(TARGET_ARCH_ARM64)
- // TODO(kustermann): Remove conservative approximation in relocator and
- // make tests precise.
- __ mov(R0, R0);
- __ mov(R0, R0);
- __ mov(R0, R0);
SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(
__ stp(LR, R1,
compiler::Address(CSP, -2 * kWordSize,
@@ -260,7 +269,18 @@
ISOLATE_UNIT_TEST_CASE(CodeRelocator_DirectForwardCall) {
RelocatorTestHelper helper(thread);
- helper.CreateInstructions({32, 36, 32});
+ const intptr_t fmax = FLAG_upper_pc_relative_call_distance;
+
+ // The gap is 8 bytes smaller than what could be directly forward-called,
+ // because the relocator's decision when to insert a trampoline is purely
+ // based on whether unresolved calls can reach such a trampoline if the next
+ // instruction is emitted (not taking into account that the next instruction
+ // might actually make some of those unresolved calls resolved).
+ helper.CreateInstructions({
+ 16, // caller (call instruction @helper.kOffsetOfCall)
+ fmax - (16 - helper.kOffsetOfCall) - 8, // 8 bytes less than maximum gap
+ 8 // forward call target
+ });
helper.EmitPcRelativeCallFunction(0, 2);
helper.EmitReturn42Function(2);
helper.BuildImageAndRunTest(
@@ -280,8 +300,13 @@
ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeForwardCall) {
RelocatorTestHelper helper(thread);
- helper.CreateInstructions(
- {32, FLAG_upper_pc_relative_call_distance - 32 + 4, 32});
+ const intptr_t fmax = FLAG_upper_pc_relative_call_distance;
+
+ helper.CreateInstructions({
+ 16, // caller (call instruction @helper.kOffsetOfCall)
+ fmax - (16 - helper.kOffsetOfCall) + 4, // 4 bytes above maximum gap
+ 8 // forwards call target
+ });
helper.EmitPcRelativeCallFunction(0, 2);
helper.EmitReturn42Function(2);
helper.BuildImageAndRunTest([&](const GrowableArray<ImageWriterCommand>&
@@ -305,7 +330,13 @@
ISOLATE_UNIT_TEST_CASE(CodeRelocator_DirectBackwardCall) {
RelocatorTestHelper helper(thread);
- helper.CreateInstructions({32, 32, 32});
+ const intptr_t bmax = -FLAG_lower_pc_relative_call_distance;
+
+ helper.CreateInstructions({
+ 8, // backwards call target
+ bmax - 8 - helper.kOffsetOfCall, // maximize out backwards call range
+ 16 // caller (call instruction @helper.kOffsetOfCall)
+ });
helper.EmitReturn42Function(0);
helper.EmitPcRelativeCallFunction(2, 0);
helper.BuildImageAndRunTest(
@@ -325,58 +356,73 @@
ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeBackwardCall) {
RelocatorTestHelper helper(thread);
- helper.CreateInstructions({32, 32, 32, 32 + 4, 32, 32, 32, 32, 32});
+ const intptr_t bmax = -FLAG_lower_pc_relative_call_distance;
+ const intptr_t fmax = FLAG_upper_pc_relative_call_distance;
+
+ helper.CreateInstructions({
+ 8, // backward call target
+ bmax - 8 - helper.kOffsetOfCall + 4, // 4 bytes exceeding backwards range
+ 16, // caller (call instruction @helper.kOffsetOfCall)
+ fmax - (16 - helper.kOffsetOfCall) -
+ 4, // 4 bytes less than forward range
+ 4,
+ 4, // out-of-range, so trampoline has to be inserted before this
+ });
helper.EmitReturn42Function(0);
- helper.EmitPcRelativeCallFunction(4, 0);
+ helper.EmitPcRelativeCallFunction(2, 0);
helper.BuildImageAndRunTest([&](const GrowableArray<ImageWriterCommand>&
commands,
uword* entry_point) {
- EXPECT_EQ(10, commands.length());
+ EXPECT_EQ(7, commands.length());
// This is the backwards call target.
EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
// This makes an out-of-range backwards call. The relocator will make the
// call go to a trampoline instead. It will delay insertion of the
// trampoline until it almost becomes out-of-range.
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[4].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[5].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[6].op);
// This is the last change the relocator thinks it can ensure the
// out-of-range call above can call a trampoline - so it injets it here and
// no later.
- EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[7].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[8].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[9].op);
+ EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[5].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[6].op);
- *entry_point = commands[4].expected_offset;
+ *entry_point = commands[2].expected_offset;
});
}
ISOLATE_UNIT_TEST_CASE(CodeRelocator_OutOfRangeBackwardCall2) {
RelocatorTestHelper helper(thread);
- helper.CreateInstructions({32, 32, 32, 32 + 4, 32});
+ const intptr_t bmax = -FLAG_lower_pc_relative_call_distance;
+
+ helper.CreateInstructions({
+ 8, // backwards call target
+ bmax - 8 - helper.kOffsetOfCall + 4, // 4 bytes exceeding backwards range
+ 16, // caller (call instruction @helper.kOffsetOfCall)
+ 4,
+ });
helper.EmitReturn42Function(0);
- helper.EmitPcRelativeCallFunction(4, 0);
+ helper.EmitPcRelativeCallFunction(2, 0);
helper.BuildImageAndRunTest(
[&](const GrowableArray<ImageWriterCommand>& commands,
uword* entry_point) {
- EXPECT_EQ(6, commands.length());
+ EXPECT_EQ(5, commands.length());
// This is the backwards call target.
EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[0].op);
EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[1].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
// This makes an out-of-range backwards call. The relocator will make
// the call go to a trampoline instead. It will delay insertion of the
- // trampoline until it almost becomes out-of-range.
- EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[4].op);
+ // trampoline until it almost becomes out-of-range (or in this case no
+ // more instructions follow).
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[2].op);
+ EXPECT_EQ(ImageWriterCommand::InsertInstructionOfCode, commands[3].op);
// There's no other instructions coming, so the relocator will resolve
// any pending out-of-range calls by inserting trampolines at the end.
- EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[5].op);
+ EXPECT_EQ(ImageWriterCommand::InsertBytesOfTrampoline, commands[4].op);
*entry_point = commands[4].expected_offset;
});
diff --git a/tests/language/const_functions/const_functions_variable_declarations_error_test.dart b/tests/language/const_functions/const_functions_variable_declarations_error_test.dart
index 56cf083..59cce22 100644
--- a/tests/language/const_functions/const_functions_variable_declarations_error_test.dart
+++ b/tests/language/const_functions/const_functions_variable_declarations_error_test.dart
@@ -16,3 +16,13 @@
var a;
return a;
}
+
+const var2 = fn2();
+// ^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+// [cfe] Constant evaluation error:
+int fn2() {
+ var x;
+ x = "string";
+ return x;
+}
diff --git a/tests/language/const_functions/const_functions_variable_declarations_test.dart b/tests/language/const_functions/const_functions_variable_declarations_test.dart
index ec6a8ec..29a87c0 100644
--- a/tests/language/const_functions/const_functions_variable_declarations_test.dart
+++ b/tests/language/const_functions/const_functions_variable_declarations_test.dart
@@ -82,6 +82,14 @@
return a;
}
+const var9 = function9();
+// ^^^^^^^^^^^
+// [analyzer] COMPILE_TIME_ERROR.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE
+int? function9() {
+ int? x;
+ return x;
+}
+
void main() {
Expect.equals(var1, 4);
Expect.equals(var1_1, 5);
@@ -92,4 +100,5 @@
Expect.equals(var6, 2);
Expect.equals(var7, 2);
Expect.equals(var8, 2);
+ Expect.equals(var9, null);
}
diff --git a/tools/VERSION b/tools/VERSION
index 749c442..8d5bbaf 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 61
+PRERELEASE 62
PRERELEASE_PATCH 0
\ No newline at end of file