Skip to content

Commit

Permalink
[flow] Part 2 Issue 3658 - Forward IfNullSet let expressions in the CFE.
Browse files Browse the repository at this point in the history
This is a small change that allows forwarding let expressions for IfNullSets in the CFE.

Since we now store the promotion information for a written expression in an if-statement, the CFE builds a let expression and it's not recognized by flow analysis. To fix this, we add a forward so flow analysis recognizes that it's the same expression that it's trying to find.

language/inference_update_4/assignment_promotion_in_if_statement_test passes from this change.

Bug: dart-lang/language#3658
Change-Id: I4ec99de036498869b0d9c5c652ca748d9f5e570a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391861
Reviewed-by: Johnni Winther <[email protected]>
Reviewed-by: Paul Berry <[email protected]>
Commit-Queue: Kallen Tu <[email protected]>
  • Loading branch information
kallentu authored and Commit Queue committed Oct 31, 2024
1 parent 490dc01 commit da9b6a7
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pkg/front_end/lib/src/type_inference/inference_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6002,6 +6002,12 @@ class InferenceVisitorImpl extends InferenceVisitorBase
replacement = new Let(readVariable, conditional)
..fileOffset = node.fileOffset;
}

// Forward the expression in cases where flow analysis needs to use the
// expression information. For example, for keeping the promotion in the
// following if statement in `if ((x ??= 2) == null) { ... }`.
flowAnalysis.forwardExpression(replacement, writeResult.expression);

return createNullAwareExpressionInferenceResult(
inferredType, replacement, nullAwareGuards);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2024, 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 the promotion of assignment expressions in if statements as described
// by https://github.com/dart-lang/language/issues/3658

int? nullableInt() => 1;

notEqualNull_assignIfNull() {
int? x = null;
if ((x ??= nullableInt()) != null) {
x.isEven; // x promoted to int
}
}

notEqualNullNull_eq() {
int? x = null;
if ((x = nullableInt()) != null) {
x.isEven; // x promoted to int
}
}

is_eq() {
int? x = null;
if ((x = nullableInt()) is int) {
x.isEven; // x promoted to int
}
}

is_plusEq() {
num x = 2;
if ((x += 1) is int) {
x.isEven; // x promoted to int
}
}

is_postfix() {
num x = 2;
if ((x++) is int) {
// No promotion because the value being is checked is the value of `x`
// before the increment, and that value isn't relevant after the increment
// occurs.
x.isEven; // Error.
}
}

is_prefix() {
num x = 2;
if ((++x) is int) {
x.isEven; // x promoted to int
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
// Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
// x.isEven; // Error.
// ^^^^^^
//
import self as self;
import "dart:core" as core;

static method nullableInt() → core::int?
return 1;
static method notEqualNull_assignIfNull() → dynamic {
has-declared-initializer core::int? x = null;
if(!((let final core::int? #t1 = x in #t1 == null ?{core::int?} x = self::nullableInt() : #t1{core::int}) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method notEqualNullNull_eq() → dynamic {
has-declared-initializer core::int? x = null;
if(!((x = self::nullableInt()) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_eq() → dynamic {
has-declared-initializer core::int? x = null;
if((x = self::nullableInt()) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_plusEq() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_postfix() → dynamic {
core::num x = 2;
if((let final core::num #t2 = x in let final core::num #t3 = x = #t2.{core::num::+}(1){(core::num) → core::num} in #t2) is core::int) {
invalid-expression "pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
x.isEven; // Error.
^^^^^^" in x{<unresolved>}.isEven;
}
}
static method is_prefix() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
// Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
// x.isEven; // Error.
// ^^^^^^
//
import self as self;
import "dart:core" as core;

static method nullableInt() → core::int?
return 1;
static method notEqualNull_assignIfNull() → dynamic {
has-declared-initializer core::int? x = null;
if(!((let final core::int? #t1 = x in #t1 == null ?{core::int?} x = self::nullableInt() : #t1{core::int}) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method notEqualNullNull_eq() → dynamic {
has-declared-initializer core::int? x = null;
if(!((x = self::nullableInt()) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_eq() → dynamic {
has-declared-initializer core::int? x = null;
if((x = self::nullableInt()) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_plusEq() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_postfix() → dynamic {
core::num x = 2;
if((let final core::num #t2 = x in let final core::num #t3 = x = #t2.{core::num::+}(1){(core::num) → core::num} in #t2) is core::int) {
invalid-expression "pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
x.isEven; // Error.
^^^^^^" in x{<unresolved>}.isEven;
}
}
static method is_prefix() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
library;
import self as self;
import "dart:core" as core;

static method nullableInt() → core::int?
;
static method notEqualNull_assignIfNull() → dynamic
;
static method notEqualNullNull_eq() → dynamic
;
static method is_eq() → dynamic
;
static method is_plusEq() → dynamic
;
static method is_postfix() → dynamic
;
static method is_prefix() → dynamic
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
// Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
// x.isEven; // Error.
// ^^^^^^
//
import self as self;
import "dart:core" as core;

static method nullableInt() → core::int?
return 1;
static method notEqualNull_assignIfNull() → dynamic {
has-declared-initializer core::int? x = null;
if(!((let final core::int? #t1 = x in #t1 == null ?{core::int?} x = self::nullableInt() : #t1{core::int}) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method notEqualNullNull_eq() → dynamic {
has-declared-initializer core::int? x = null;
if(!((x = self::nullableInt()) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_eq() → dynamic {
has-declared-initializer core::int? x = null;
if((x = self::nullableInt()) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_plusEq() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_postfix() → dynamic {
core::num x = 2;
if((let final core::num #t2 = x in let final core::num #t3 = x = #t2.{core::num::+}(1){(core::num) → core::num} in #t2) is core::int) {
invalid-expression "pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
x.isEven; // Error.
^^^^^^" in x{<unresolved>}.isEven;
}
}
static method is_prefix() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
int? nullableInt() => 1;

notEqualNull_assignIfNull() {}

notEqualNullNull_eq() {}

is_eq() {}

is_plusEq() {}

is_postfix() {}

is_prefix() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
int? nullableInt() => 1;

is_eq() {}

is_plusEq() {}

is_postfix() {}

is_prefix() {}

notEqualNullNull_eq() {}

notEqualNull_assignIfNull() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// Problems in component:
//
// sdk/lib/core/core.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/async/async.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/collection/collection.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/_internal/vm_shared/lib/compact_hash.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/concurrent/concurrent.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/convert/convert.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/developer/developer.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/ffi/ffi.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/internal/internal.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/isolate/isolate.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/math/math.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/mirrors/mirrors.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/typed_data/typed_data.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/_internal/vm/bin/vmservice_io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/vmservice/vmservice.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/_internal/vm/bin/builtin.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/html/dartium/nativewrappers.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/io/io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
// sdk/lib/cli/cli.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
//
library;
//
// Problems in library:
//
// pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
// Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
// x.isEven; // Error.
// ^^^^^^
//
import self as self;
import "dart:core" as core;

static method nullableInt() → core::int?
return 1;
static method notEqualNull_assignIfNull() → dynamic {
has-declared-initializer core::int? x = null;
if(!((let final core::int? #t1 = x in #t1 == null ?{core::int?} x = self::nullableInt() : #t1{core::int}) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method notEqualNullNull_eq() → dynamic {
has-declared-initializer core::int? x = null;
if(!((x = self::nullableInt()) == null)) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_eq() → dynamic {
has-declared-initializer core::int? x = null;
if((x = self::nullableInt()) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_plusEq() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
static method is_postfix() → dynamic {
core::num x = 2;
if((let final core::num #t2 = x in let final core::num #t3 = x = #t2.{core::num::+}(1){(core::num) → core::num} in #t2) is core::int) {
invalid-expression "pkg/front_end/testcases/inference_update_4/assignment_promotion_in_if_statement.dart:44:7: Error: The getter 'isEven' isn't defined for the class 'num'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'isEven'.
x.isEven; // Error.
^^^^^^" in x{<unresolved>}.isEven;
}
}
static method is_prefix() → dynamic {
core::num x = 2;
if((x = x.{core::num::+}(1){(core::num) → core::num}) is core::int) {
x{core::int}.{core::int::isEven}{core::bool};
}
}
Loading

0 comments on commit da9b6a7

Please sign in to comment.