Skip to content

Commit

Permalink
#2976. Add type inference tests. Part 2 (#3005)
Browse files Browse the repository at this point in the history
Add type inference tests. Part 2
  • Loading branch information
sgrekhov authored Dec 6, 2024
1 parent 2d1547e commit 44b0733
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 0 deletions.
46 changes: 46 additions & 0 deletions LanguageFeatures/Static-access-shorthand/grammar_A03_t02.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.

/// @assertion We also add . to the tokens that an expression statement cannot
/// start with. This doesn't affect starting with a double literal like `.42`,
/// since that's a different token than a single `.`.
///
/// @description Checks that it is a compile-time error if an expression
/// statement is started with `.`.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

void foo() {}

class C {}

enum E {e0;}

main() {
.new;
//^
// [analyzer] unspecified
// [cfe] unspecified

.new();
//^
// [analyzer] unspecified
// [cfe] unspecified

.C();
//^
// [analyzer] unspecified
// [cfe] unspecified

.foo();
//^
// [analyzer] unspecified
// [cfe] unspecified

.e0;
//^
// [analyzer] unspecified
// [cfe] unspecified
}
71 changes: 71 additions & 0 deletions LanguageFeatures/Static-access-shorthand/semantics_A05_t01.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.

/// @assertion Dart semantics, static and dynamic, do not follow the grammar
/// precisely. For example, a static member invocation expression of the form
/// `C.id<T1>(e2)` is treated as an atomic entity for type inference (and
/// runtime semantics). It’s not a combination of doing a `C.id` tear-off, then
/// a `<T1>` instantiation and then an `(e2)` invocation. The context type of
/// that entire expression is used throughout the inference, where
/// `(e1.id<T1>)(e2)` has `(e1.id<T1>)` in a position where it has no context
/// type.
///
/// Because of that, the specification of the static and runtime semantics of
/// the new constructs needs to address all the forms `.id`, `.id<typeArgs>`,
/// `.id(args)`, `.id<typeArgs>(args)`, `.new` or `.new(args)`.
/// ...
/// The general rule is that any of the expression forms above, starting with
/// `.id`, are treated exactly as if they were preceded by a fresh prefixed
/// identifier `_p.C` which denotes the declaration of the type of the context
/// type scheme of the entire `<staticMemberShorthand>`.
///
/// @description Checks that a named or unnamed constructor can be torn-off
/// using a shorthand syntax.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

import '../../Utils/expect.dart';

class C<T> {
T t;
C(this.t);
C.id(this.t);
}

extension type ET<T>(T t) {
ET.id(this.t);
}

main() {
Object? o = C<int>(0);
if (o is C<int>) {
o = .new;
if (o is Function) {
Expect.equals(1, o(1).t);
}
}
o = C(0);
if (o is C) {
o = .id;
if (o is Function) {
Expect.equals(2, o(2).t);
}
}

o = ET<int>(0);
if (o is ET) {
o = .new;
if (o is Function) {
Expect.equals(3, o(3));
}
}
o = ET<int>(0);
if (o is ET) {
o = .id;
if (o is Function) {
Expect.equals(4, o(4));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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.

/// @assertion Declaration denoted by a type scheme A context type scheme is
/// said to denote a declaration in some cases. Not all context type schemes
/// denote a declaration.
/// If a type scheme `S`:
/// - has the form `C` or `C<typeArgs>` where `C` is a type introduced by a
/// declaration `D` which must therefore be a type-introducing declaration,
/// which currently means a class, mixin, enum or extension type declaration,
/// then `S` denotes the declaration `D`.
/// - has the form `S?` or `FutureOr<S>`, and the type scheme S denotes a
/// declaration D, then so does `S?/FutureOr<S>`. Only the "base type" of the
/// union type is considered, ensuring that a type scheme denotes at most one
/// declaration or static namespace.
/// - has any other form, including type variables, promoted type variables and
/// `_`, then the type scheme does not denote any declaration or namespace.
///
/// @description Checks that in case of context type `_` the type scheme does
/// not denote any declaration and it is a compile-time error to use a shorthand
/// in this case.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

main() {
int v1 = (.parse("42")).abs();
// ^
// [analyzer] unspecified
// [cfe] unspecified

int v2 = .parse("42") + 1;
// ^
// [analyzer] unspecified
// [cfe] unspecified

dynamic v3 = .parse("42");
// ^
// [analyzer] unspecified
// [cfe] unspecified
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.

/// @assertion Expressions of the forms `.new<typeArgs>(args)` or
/// `.new<typeArgs>` (as a prefix of a `<staticMemberShorthand> <selector>*`
/// production, or the entire chain) are compile-time errors, just like the
/// corresponding `T.new<typeArgs>(args)` and `T.new<typeArgs>` already are,
/// whether used as instantiated tear-off or invoked.
///
/// @description Checks that it is a compile-time error to use a shorthand
/// expression of the form `.new<typeArgs>`.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

class C<T> {
C();
}

extension type ET<T>(T v) {}

main() {
Object? o = C();
if (o is C) {
o = .new<int>;
// ^
// [analyzer] unspecified
// [cfe] unspecified
}

o = ET(0);
if (o is ET) {
o = .new<int>;
// ^
// [analyzer] unspecified
// [cfe] unspecified
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.

/// @assertion The invocation of a constructor is not using an instantiated
/// type, it’s behaving as if the constructor was preceded by a raw type, which
/// type inference should then infer type arguments for. Doing
/// `List<int> l = .filled(10, 10);` works like doing
/// `List<int> l = List.filled(10, 10);`, and it is the following downwards
/// inference with context type `List<int>` that makes it into
/// `List<int>.filled(10, 10);`. This distinction matters for something like:
/// ```
/// List<String> l =
/// .generate(10, (int i) => i + 1).map((x) => x.toRadixString(16)).toList();
/// ```
/// which is equivalent to inserting `List` in front of `.generate`, which will
/// then be inferred as `List<int>`. In most normal use cases it doesn't matter,
/// because the context type will fill in the missing type variables, but if the
/// construction is followed by more selectors, it loses that context type.
///
/// @description Checks that in case of a constructor invocation the type
/// inference occurs as if the shorthand expression is preceded by the raw type
/// and then a type inference infers the type arguments.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

import '../../Utils/expect.dart';

class C<T> {
T value;
C(this.value);
C.id(this.value);

C<int> toInt(int v) => C<int>(v);
}

extension type ET<T>(T v) {
ET.id(this.v);

ET<int> toInt(int v) => ET<int>(v);
}

main() {
C<int> c1 = .new("String").toInt(1);
Expect.equals(1, c1.value);

C<int> c2 = .id("String").toInt(2);
Expect.equals(1, c2.value);

ET<int> et1 = .new("String").toInt(3);
Expect.equals(3, et1.v);

ET<int> et2 = .id("String").toInt(4);
Expect.equals(4, et2.v);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.

/// @assertion The invocation of a constructor is not using an instantiated
/// type, it’s behaving as if the constructor was preceded by a raw type, which
/// type inference should then infer type arguments for. Doing
/// `List<int> l = .filled(10, 10);` works like doing
/// `List<int> l = List.filled(10, 10);`, and it is the following downwards
/// inference with context type `List<int>` that makes it into
/// `List<int>.filled(10, 10);`. This distinction matters for something like:
/// ```
/// List<String> l =
/// .generate(10, (int i) => i + 1).map((x) => x.toRadixString(16)).toList();
/// ```
/// which is equivalent to inserting `List` in front of `.generate`, which will
/// then be inferred as `List<int>`. In most normal use cases it doesn’t matter,
/// because the context type will fill in the missing type variables, but if the
/// construction is followed by more selectors, it loses that context type.
///
/// @description Checks that in case of a constructor invocation the type
/// inference occurs as if the shorthand expression is preceded by the raw type
/// and then a type inference infers the type arguments.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

import '../../Utils/expect.dart';

main() {
List<String> l =
.generate(10, (int i) => i + 1).map((x) => x.toRadixString(16)).toList();
Expect.listEquals(["1", "2", "3", "4", "5", "6", "7", "8", "9", "a"], l);

num n = .parse("42").abs();
Expect.equals(42, n);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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.

/// @assertion Since `!` does propagate a context,
/// `int x = (.tryParse(input))!;` does work, with a context type scheme of
/// `int?`, which is enough to allow `.tryParse`. Same for
/// `int x = .tryParse(input) ?? 0;` which gives the first operand the context
/// type `int?`.
///
/// @description Checks that `!` selector propagates the context type in case of
/// shorthand expression.
/// @author [email protected]
// SharedOptions=--enable-experiment=enum-shorthands

import '../../Utils/expect.dart';

class C {
int value;
C(this.value);

static C? get id => C(0);
}

mixin M on C {
static M? get id => MC(1);
}
class MC = C with M;

enum E {
e0(2);
final int value;
const E(this.value);

static E? get id => E.e0;
}

extension type ET(int v) {
static ET get id => ET(3);
}

main() {
C c = .id!;
Expect.equals(0, c.value);

c = (.id)!;
Expect.equals(0, c.value);

M m = .id!;
Expect.equals(1, m.value);

m = (.id)!;
Expect.equals(1, m.value);

E e = .id!;
Expect.equals(2, e.value);

e = (.id)!;
Expect.equals(2, e.value);

ET et = .id!;
Expect.equals(3, et.v);

et = (.id)!;
Expect.equals(3, et.v);
}
Loading

0 comments on commit 44b0733

Please sign in to comment.