-
Notifications
You must be signed in to change notification settings - Fork 28
Fixes #1477. [Records] Type inference tests updated #1500
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
Conversation
@eernstg please review |
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.
Looks good, except that in a couple of cases I can't see that the given code actually tests what we need to test.
|
||
main() { | ||
(num,) r1 = (42,); | ||
r1.$0.isOdd; |
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.
I'm trying to understand how this is a test that confirms any property of type inference.
The type of 42
is int
(we do run type inference on that expression, but it doesn't do anything).
Then the getter $0
of r1
has static return type num
, because the static type of r1
in (num,)
, and this means that r1.$0.isOdd
should be a compile-time error.
In order to see that type inference does take place would be to use record literals containing expressions that need type inference. For example:
class C<X> {
Type get typeArgument => X;
}
class D<X extends Y, Y> extends C<Y> {}
void main() {
(C<int>,) r1 = (D(),);
Expect.equals(int, r1.$0.typeArgument);
}
This would test that type inference actually finds Y == int
and then X == int
such that D()
infers as D<int, int>()
, just like it would have inferred D()
outside a record literal.
We should have cases for the other coercions, as well (but they may be below in this PR, I have only seen it until this point for now).
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.
The type of 42 is int (we do run type inference on that expression, but it doesn't do anything).
Then the getter $0 of r1 has static return type num, because the static type of r1 in (num,), and this means that r1.$0.isOdd should be a compile-time error.
But doesn't it contradict the Type inference part of the spec?
As noted above, contrary to the practice for runtime checked covariant nominal types, we do not prefer the context type over the more precise upwards type. The following example illustrates this:
// No static error. // Inferred type of the record is (int, double) (num, num) r = (3, 3.5)..$0.isEven;
And this test tests this statement from the spec. Did I miss anything?
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.
There is no contradiction in this situation: The type inference step in Dart is a program transformation that provides actual type arguments to invocations of generic functions where no actual type arguments are provided in the source code, and it adds type annotations to formal parameters of certain function literals where those type annotations have been omitted, and it infers a return type for certain function literals (and there are some other cases, e.g., it may infer the actual type argument(s) of a collection literal). A similar step is the program transformation that in some situations adds actual type arguments to an expression whose type is a generic function type ('generic function instantiation').
In other words, type inference (and other coercions) can change an expression, including the initializing expression of a variable declaration.
There is a completely separate mechanism whereby the declared type of a variable is inferred (when the source code does not specify any such declared type, e.g., var x = e;
). This can also be considered to be a program transformation (changing var x = e;
to T x = e;
for some T
).
Inference of the declared type of a variable never occurs in the case where a declared type is present in the source code.
For line 34, type inference on (42,)
yields (42,)
(a no-op), and no type inference of the type of r1
occurs. So it's all a no-op, and the type of r1
is (num,)
. Hence, r1.$0.isOdd
is a compile-time error.
In the example I gave, type inference on (D(),)
yields (D<int, int>(),)
, and the test on r1.$0.typeArgument
serves to confirm that the run-time semantics corresponds to the transformation that the type inference is expected to have performed.
So in this case there is non-trivial type inference on the initializing expression, and we are investigating the outcome (somewhat indirectly, because we can't directly watch type inference at work).
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.
Thank you for detailed explanation! I updated tests accordingly.
r1.$0.isOdd; | ||
|
||
({num n}) r2 = (n: 42); | ||
r2.n.isOdd; |
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.
Same issue here: It's a compile-time error, and it doesn't actually test type inference. Same in line 41-42.
dynamic d = 3; | ||
(num, double, int Function(int), void Function(num)) r = (d, 3, id, c); | ||
r.expectStaticType< | ||
Exactly<((num, double, int Function(int), void Function(num)))>>(); |
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 is tricky. First, r
has the declared type (num, double, int Function(int), void Function(num))
because we've declared it explicitly (no type inference needed).
Next, we do infer (d, 3, id, c)
with this type as the context type, and that does introduce the cast d as num
, it changes id
to id<int>
, and it changes c
to c.call
.
All of that is great, but line 45-46 doesn't serve any purpose that I can see.
The fact that line 44 isn't a compile-time error implies that the static type of (d, 3, id, c)
in that context is assignable to the declared type, and I think we can consider that to serve as evidence that the static type of (d, 3, id, c)
is OK after type inference. (By the way, I think it could be tested using (d, 3, id, c)..expectStaticType<...>()
.)
But we'd still like to confirm dynamically that the coercions did actually take place.
For the insertion of the cast from dynamic
, we could have a variant dynamic d2 = 1.5;
and an assignment (int, double, Function, Function) r2 = (d2, 3, id, c);
, which would be expected to throw at this cast.
For the fact that (in line 44) 3
is an integer literal with static type double
, we'd have to distinguish between web and native platforms, and on the native platforms we could test that (r2.$1 as dynamic).isEven
throws.
Similarly for the generic function instantiation on id
, and the .call
insertion on c
.
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.
Runtime checks added
main() { | ||
(List<int>, Map<String, int>, Set<String>) r = ([], {}, {}); | ||
r.expectStaticType< | ||
Exactly<((List<int>, Map<String, int>, Set<String>))>>(); |
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.
Same issues as previous test: Line 41-42 doesn't do anything useful as far as I can see, but we should (1) test the static type of ([], {}, {})
in line 40 using ..expectStaticType
as previously, and (2) we should check that the list/set/map does get a run-time type which is a subtype of List<int>
, Map<String, int>
, respectively Set<String>
.
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.
Updated
(List<int>, Map<String, int>, Set<String>) r = ([], {}, {}); | ||
r.expectStaticType< | ||
Exactly<((List<int>, Map<String, int>, Set<String>))>>(); | ||
bar(([], {}, {})); |
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.
..expectStaticType
again. The dynamic types can be tested in the body of bar
.
/// If K is any other type schema: | ||
/// | ||
/// - Each ei is inferred with context type schema _ to have type Ti | ||
/// - The type of E is (T1, ..., Tn, {d1 : T{n+1}, ...., dm : T{n+m}}) | ||
/// | ||
/// @description Checks horizontal inference for records. |
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.
I don't think horizontal inference is applicable to record literals (at least, I don't remember hearing anything about it). But it sounds like an interesting additional feature, so it's great that we have it on the table! I'm creating a new language repo issue about this question.
Next, I can see that this test is actually about a situation involving a generic function and an invocation thereof, and the new thing is that some types involved in the associated horizontal inference for that function invocation are record types. Very good, we need that!
As far as I can see, we have the stages {b, c, d}
and {a}
, and T
and U
are as stated. So that looks good!
See dart-lang/language#2578 as well, we may need some additional tests (possibly that it doesn't work, if the language and implementation teams think it should not be supported, or that it should wait).
…rding to the review notes
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.
Looks good, just one question remaining.
@@ -31,13 +31,11 @@ | |||
// SharedOptions=--enable-experiment=records | |||
|
|||
main() { | |||
(num,) r1 = (42,); | |||
r1.$0.isOdd; | |||
(num,) r1 = (42,) ..$0.isOdd; |
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.
Ah, yes, we can do that, cool!
Exactly<((num, double, int Function(int), void Function(num)))>>(); | ||
(num, double, int Function(int), void Function(num)) r1 = (d, 3, id, c) | ||
..expectStaticType< | ||
Exactly<(num, double, int Function(int), void Function(num))>>(); |
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.
That's interesting! I think it's a correct expectation that the first positional variable initializer d
gets transformed into d as num
, and hence the record type has num
as its first positional type.
I think this is interesting because it tests the detailed ordering of the effects: If we had instead transformed the initializing expression into (d, 3, id, c) as (num, <and more>)
then the expectStaticType
would see dynamic
as the declared type of the first positional variable of the record literal. So they might have to change some things in the analyzer/CFE in order to make things happen in the specified order.
main() { | ||
(C<int>, {C<double> x}) r = (D(), x: D()); | ||
Expect.equals(int, r.$0.typeArgument); | ||
Expect.equals(int, r.x.typeArgument); |
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.
Wouldn't that expectation be double
rather than int
?
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.
Of course double
. Thank you for noticing
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.
LGTM
2022-10-21 [email protected] Fixes dart-lang/co19#1511. [Records] Remove `records` experimental flag from legacy libraries (dart-lang/co19#1512) 2022-10-21 [email protected] Fixes dart-lang/co19#1477. [Records] Type inference tests updated (dart-lang/co19#1500) 2022-10-21 [email protected] Fixes dart-lang/co19#1505. [Records] Added tests for dynamic access to record fields (dart-lang/co19#1507) 2022-10-19 [email protected] dart-lang/co19#1399. [Records] Interaction with legacy tests added (dart-lang/co19#1502) 2022-10-19 [email protected] dart-lang/co19#195. Typos fixed (dart-lang/co19#1508) 2022-10-19 [email protected] dart-lang/co19#993. [ffi] nullptr test added (dart-lang/co19#1506) 2022-10-14 [email protected] Fix typos (dart-lang/co19#1504) 2022-10-12 [email protected] Fixes dart-lang/co19#1427. Expando tests added (dart-lang/co19#1479) 2022-10-12 [email protected] dart-lang/co19#1399. [Records] More members tests added (dart-lang/co19#1501) 2022-10-12 49699333+dependabot[bot]@users.noreply.github.com Bump actions/checkout from 3.0.2 to 3.1.0 (dart-lang/co19#1499) 2022-10-11 [email protected] dart-lang/co19#1400. [Views] Syntax tests added (dart-lang/co19#1498) 2022-10-07 [email protected] Fixes dart-lang/co19#1491. Use correct record type in 'as' expression (dart-lang/co19#1497) 2022-10-07 [email protected] Fixes dart-lang/co19#1489. Don't use '?' in expressions (dart-lang/co19#1495) 2022-10-07 [email protected] Fixes dart-lang/co19#1488. Use correct record types (dart-lang/co19#1493) 2022-10-07 [email protected] Fixes dart-lang/co19#1490. Missing parameters names added (dart-lang/co19#1496) Change-Id: I6d26701ee14a0c7820e3c3094d7d850c0f914028 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/265087 Reviewed-by: Alexander Thomas <[email protected]>
No description provided.