Skip to content

Commit c8141b5

Browse files
authored
Remove ExpectType and some calls to IsImplicitlyConvertible. (#2647)
These functions are dangerous, as they check whether conversions are possible without actually performing the conversions. In each case where they were used, explorer would crash in some cases if a user-defined conversion is required. This change moves us more towards implicit conversions being handled by a regular function call on an interface and away from them being magical builtins. Unfortunately, this exposes a pre-existing bug that a call of the form `x.(ImplicitAs(T).Convert)()` compiles even if `x` only has an explicit conversion to `T`. That's worked around here for now, but will need a proper fix later.
1 parent 21c3f64 commit c8141b5

16 files changed

+152
-53
lines changed

explorer/interpreter/type_checker.cpp

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,18 @@ auto TypeChecker::BuildBuiltinMethodCall(const ImplScope& impl_scope,
795795
CARBON_ASSIGN_OR_RETURN(Nonnull<const InterfaceType*> iface_type,
796796
GetBuiltinInterfaceType(source_loc, interface));
797797

798+
if (interface.builtin == Builtins::ImplicitAs) {
799+
// Type-checking the below expression resolves the member name to
800+
// `As(Destination).Convert`, which allows both implicit and explicit
801+
// conversions. So manually check that `ImplicitAs(Destination)` is
802+
// actually implemented.
803+
// TODO: This check should be performed as part of type-checking the
804+
// compound member access expression below. This is a short-term
805+
// workaround.
806+
CARBON_RETURN_IF_ERROR(impl_scope.Resolve(
807+
iface_type, &source->static_type(), source->source_loc(), *this));
808+
}
809+
798810
// Build an expression to perform the call `source.(interface.method)(args)`.
799811
Nonnull<Expression*> iface_expr = arena_->New<ValueLiteral>(
800812
source_loc, iface_type, arena_->New<TypeType>(), ValueCategory::Let);
@@ -839,23 +851,6 @@ auto TypeChecker::ExpectNonPlaceholderType(SourceLocation source_loc,
839851
CARBON_FATAL() << "unknown kind of placeholder type " << *type;
840852
}
841853

842-
auto TypeChecker::ExpectType(SourceLocation source_loc,
843-
std::string_view context,
844-
Nonnull<const Value*> expected,
845-
Nonnull<const Value*> actual,
846-
const ImplScope& impl_scope) const
847-
-> ErrorOr<Success> {
848-
if (!IsImplicitlyConvertible(actual, expected, impl_scope,
849-
/*allow_user_defined_conversions=*/true)) {
850-
return ProgramError(source_loc)
851-
<< "type error in " << context << ": "
852-
<< "'" << *actual << "' is not implicitly convertible to '"
853-
<< *expected << "'";
854-
} else {
855-
return Success();
856-
}
857-
}
858-
859854
// Argument deduction matches two values and attempts to find a set of
860855
// substitutions into deduced bindings in one of them that would result in the
861856
// other.
@@ -2986,6 +2981,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
29862981
Nonnull<const ConstraintType*> iface_constraint,
29872982
ConvertToConstraintType(access.source_loc(),
29882983
"compound member access", *iface));
2984+
// TODO: We should check that the base type implements the specified
2985+
// interface, not only the interface containing the member.
2986+
// `x.(ImplicitAs(T).Convert)()` should require that the type of `x`
2987+
// implements `ImplicitAs(T)`, not only `As(T)`.
29892988
CARBON_ASSIGN_OR_RETURN(witness,
29902989
impl_scope.Resolve(iface_constraint, *base_type,
29912990
e->source_loc(), *this));
@@ -3271,13 +3270,17 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
32713270
op.set_static_type(&cast<PointerType>(*ts[0]).pointee_type());
32723271
op.set_value_category(ValueCategory::Var);
32733272
return Success();
3274-
case Operator::Ptr:
3275-
CARBON_RETURN_IF_ERROR(ExpectType(e->source_loc(), "*",
3276-
arena_->New<TypeType>(), ts[0],
3277-
impl_scope));
3273+
case Operator::Ptr: {
3274+
auto* type_type = arena_->New<TypeType>();
3275+
CARBON_ASSIGN_OR_RETURN(
3276+
Nonnull<Expression*> converted,
3277+
ImplicitlyConvert("pointee type", impl_scope, op.arguments()[0],
3278+
type_type));
3279+
op.arguments()[0] = converted;
32783280
op.set_static_type(arena_->New<TypeType>());
32793281
op.set_value_category(ValueCategory::Let);
32803282
return Success();
3283+
}
32813284
case Operator::AddressOf:
32823285
if (op.arguments()[0]->value_category() != ValueCategory::Var) {
32833286
return ProgramError(op.arguments()[0]->source_loc())
@@ -3436,10 +3439,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
34363439
return ProgramError(e->source_loc())
34373440
<< "__intrinsic_assert takes 2 arguments";
34383441
}
3439-
CARBON_RETURN_IF_ERROR(ExpectType(
3442+
CARBON_RETURN_IF_ERROR(ExpectExactType(
34403443
e->source_loc(), "__intrinsic_assert argument 0",
34413444
arena_->New<BoolType>(), &args[0]->static_type(), impl_scope));
3442-
CARBON_RETURN_IF_ERROR(ExpectType(
3445+
CARBON_RETURN_IF_ERROR(ExpectExactType(
34433446
e->source_loc(), "__intrinsic_assert argument 1",
34443447
arena_->New<StringType>(), &args[1]->static_type(), impl_scope));
34453448
e->set_static_type(TupleType::Empty());
@@ -3978,10 +3981,9 @@ auto TypeChecker::TypeCheckPattern(
39783981
<< "conversion to type succeeded but didn't produce a type, got "
39793982
<< *type;
39803983
if (expected) {
3981-
if (IsConcreteType(type)) {
3982-
CARBON_RETURN_IF_ERROR(ExpectType(p->source_loc(), "name binding",
3983-
type, *expected, impl_scope));
3984-
} else {
3984+
// TODO: Per proposal #2188, we should be performing conversions at
3985+
// this level rather than on the overall initializer.
3986+
if (!IsConcreteType(type)) {
39853987
BindingMap generic_args;
39863988
if (!PatternMatch(type, *expected, binding.type().source_loc(),
39873989
std::nullopt, generic_args, trace_stream_,
@@ -4063,11 +4065,8 @@ auto TypeChecker::TypeCheckPattern(
40634065
<< "alternative pattern does not name a choice type.";
40644066
}
40654067
const auto& choice_type = cast<ChoiceType>(*type);
4066-
if (expected) {
4067-
CARBON_RETURN_IF_ERROR(ExpectType(alternative.source_loc(),
4068-
"alternative pattern", &choice_type,
4069-
*expected, impl_scope));
4070-
}
4068+
// TODO: Per proposal #2188, we should perform an implicit conversion on
4069+
// the scrutinee if a choice type is provided.
40714070
std::optional<Nonnull<const AlternativeSignature*>> signature =
40724071
choice_type.declaration().FindAlternative(
40734072
alternative.alternative_name());
@@ -4098,6 +4097,7 @@ auto TypeChecker::TypeCheckPattern(
40984097
auto& expression = cast<ExpressionPattern>(*p).expression();
40994098
CARBON_RETURN_IF_ERROR(TypeCheckExp(&expression, impl_scope));
41004099
p->set_static_type(&expression.static_type());
4100+
// TODO: Per proposal #2188, we should form an `==` comparison here.
41014101
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> expr_value,
41024102
InterpExp(&expression, arena_, trace_stream_));
41034103
p->set_value(expr_value);
@@ -4299,7 +4299,10 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
42994299
TypeCheckPattern(&for_stmt.variable_declaration(),
43004300
&cast<StaticArrayType>(rhs).element_type(),
43014301
inner_impl_scope, ValueCategory::Var));
4302-
4302+
CARBON_RETURN_IF_ERROR(ExpectExactType(
4303+
for_stmt.source_loc(), "`for` pattern",
4304+
&cast<StaticArrayType>(rhs).element_type(),
4305+
&for_stmt.variable_declaration().static_type(), impl_scope));
43034306
} else {
43044307
return ProgramError(for_stmt.source_loc())
43054308
<< "expected array type after in, found value of type " << rhs;

explorer/interpreter/type_checker.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -410,15 +410,6 @@ class TypeChecker {
410410
auto IsSameType(Nonnull<const Value*> type1, Nonnull<const Value*> type2,
411411
const ImplScope& impl_scope) const -> bool;
412412

413-
// Check whether `actual` is implicitly convertible to `expected`
414-
// and halt with a fatal compilation error if it is not.
415-
//
416-
// TODO: Does not actually perform the conversion if a user-defined
417-
// conversion is needed. Should be used very rarely for that reason.
418-
auto ExpectType(SourceLocation source_loc, std::string_view context,
419-
Nonnull<const Value*> expected, Nonnull<const Value*> actual,
420-
const ImplScope& impl_scope) const -> ErrorOr<Success>;
421-
422413
// Check whether `actual` is the same type as `expected` and halt with a
423414
// fatal compilation error if it is not.
424415
auto ExpectExactType(SourceLocation source_loc, std::string_view context,

explorer/testdata/array/fail_size_mismatch.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
package ExplorerTest api;
1010

1111
fn Main() -> i32 {
12-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/array/fail_size_mismatch.carbon:[[@LINE+1]]: type error in name binding: '(i32, i32, i32)' is not implicitly convertible to '[i32; 2]'
12+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/array/fail_size_mismatch.carbon:[[@LINE+1]]: type error in initializer of variable: '(i32, i32, i32)' is not implicitly convertible to '[i32; 2]'
1313
var x: [i32; 2] = (0, 1, 2);
1414
return x[0];
1515
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// RUN: %{not} %{explorer-run}
7+
// RUN: %{not} %{explorer-run-trace}
8+
9+
package ExplorerTest api;
10+
11+
class ConvertTo(T:! type) {
12+
var v: T;
13+
impl as ImplicitAs(T) {
14+
fn Convert[self: Self]() -> T { return self.v; }
15+
}
16+
}
17+
18+
fn Main() -> i32 {
19+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assert/fail_convert.carbon:[[@LINE+3]]: type error in __intrinsic_assert argument 0
20+
// CHECK:STDERR: expected: bool
21+
// CHECK:STDERR: actual: class ConvertTo(T = bool)
22+
__intrinsic_assert({.v = true} as ConvertTo(bool), {.v = "Pass"} as ConvertTo(String));
23+
__intrinsic_assert({.v = false} as ConvertTo(bool), {.v = "Fail"} as ConvertTo(String));
24+
return 0;
25+
}

explorer/testdata/class/fail_direct_base_class_init.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class B extends A {
1717
}
1818

1919
fn Main() -> i32 {
20-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_direct_base_class_init.carbon:[[@LINE+1]]: type error in name binding: '{.a: i32, .b: i32}' is not implicitly convertible to 'class B'
20+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_direct_base_class_init.carbon:[[@LINE+1]]: type error in initializer of variable: '{.a: i32, .b: i32}' is not implicitly convertible to 'class B'
2121
var b: B = {.a=0, .b=1};
2222
return 0;
2323
}

explorer/testdata/class/fail_field_mismatch.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Point {
1414
}
1515

1616
fn Main() -> i32 {
17-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_mismatch.carbon:[[@LINE+1]]: type error in name binding: '{.x: i32, .z: i32}' is not implicitly convertible to 'class Point'
17+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_mismatch.carbon:[[@LINE+1]]: type error in initializer of variable: '{.x: i32, .z: i32}' is not implicitly convertible to 'class Point'
1818
var p: Point = {.x = 1, .z = 2};
1919
return p.x - 1;
2020
}

explorer/testdata/class/fail_field_missing.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Point {
1414
}
1515

1616
fn Main() -> i32 {
17-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_missing.carbon:[[@LINE+1]]: type error in name binding: '{.x: i32}' is not implicitly convertible to 'class Point'
17+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_field_missing.carbon:[[@LINE+1]]: type error in initializer of variable: '{.x: i32}' is not implicitly convertible to 'class Point'
1818
var p: Point = {.x = 1};
1919
return p.x - 1;
2020
}

explorer/testdata/class/fail_invalid_subtyping.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class D {
1616

1717
fn Main() -> i32 {
1818
var d: D = {};
19-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_invalid_subtyping.carbon:[[@LINE+1]]: type error in name binding: 'class D*' is not implicitly convertible to 'class C*'
19+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_invalid_subtyping.carbon:[[@LINE+1]]: type error in initializer of variable: 'class D*' is not implicitly convertible to 'class C*'
2020
var c: C* = &d;
2121
return 0;
2222
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// RUN: %{not} %{explorer-run}
7+
// RUN: %{not} %{explorer-run-trace}
8+
9+
package ExplorerTest api;
10+
11+
class IntLike {
12+
var n: i32;
13+
}
14+
15+
impl i32 as ImplicitAs(IntLike) {
16+
fn Convert[self: i32]() -> IntLike { return {.n = self}; }
17+
}
18+
19+
fn Main() -> i32 {
20+
var arr: [i32; 4] = (0, 1, 2, 3);
21+
// TODO: We should accept this, but currently have nowhere in our
22+
// representation to describe the conversion.
23+
for (x: IntLike in arr) {
24+
Print("{0}", x.n);
25+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/for/fail_convert.carbon:[[@LINE+3]]: type error in `for` pattern
26+
// CHECK:STDERR: expected: i32
27+
// CHECK:STDERR: actual: class IntLike
28+
}
29+
return 0;
30+
}

explorer/testdata/generic_class/fail_point_equal.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Point(T:! type) {
1515

1616
fn Main() -> i32 {
1717
var p: Point(i32) = {.x = 0, .y = 0};
18-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_point_equal.carbon:[[@LINE+1]]: type error in name binding: 'class Point(T = i32)' is not implicitly convertible to 'class Point(T = bool)'
18+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_point_equal.carbon:[[@LINE+1]]: type error in initializer of variable: 'class Point(T = i32)' is not implicitly convertible to 'class Point(T = bool)'
1919
var q: Point(bool) = p;
2020
return 0;
2121
}

explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class A {}
1313
fn Main() -> i32 {
1414
var a: A = {};
1515
var b: A* = &a;
16-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon:[[@LINE+1]]: type error in name binding: 'class A*' is not implicitly convertible to 'i32'
16+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon:[[@LINE+1]]: type error in initializer of variable: 'class A*' is not implicitly convertible to 'i32'
1717
var c: i32 = b;
1818
return 1;
1919
}

explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class A {}
1313
fn Main() -> i32 {
1414
var a: i32 = 0;
1515
var b: i32* = &a;
16-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon:[[@LINE+1]]: type error in name binding: 'i32*' is not implicitly convertible to 'class A*'
16+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon:[[@LINE+1]]: type error in initializer of variable: 'i32*' is not implicitly convertible to 'class A*'
1717
var c: A* = b;
1818
return 1;
1919
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// RUN: %{explorer-run}
7+
// RUN: %{explorer-run-trace}
8+
// CHECK:STDOUT: result: 1
9+
10+
package ExplorerTest api;
11+
12+
class TypeLike {
13+
var v: type;
14+
impl as ImplicitAs(type) {
15+
fn Convert[self: Self]() -> type { return i32; }
16+
}
17+
}
18+
19+
fn Almosti32() -> TypeLike { return {.v = i32}; }
20+
21+
fn Main() -> i32 {
22+
var a: Almosti32() = 1;
23+
var p: Almosti32()* = &a;
24+
return *p;
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// RUN: %{explorer-run}
7+
// RUN: %{explorer-run-trace}
8+
// CHECK:STDOUT: 3
9+
// CHECK:STDOUT: 4
10+
// CHECK:STDOUT: result: 0
11+
12+
package ExplorerTest api;
13+
14+
fn Main() -> i32 {
15+
var a: (i32, i32) = (1, 2);
16+
var p: (i32, i32)* = &a;
17+
18+
a[0] = 3;
19+
Print("{0}", (*p)[0]);
20+
21+
(*p)[1] = 4;
22+
Print("{0}", a[1]);
23+
24+
return 0;
25+
}

explorer/testdata/tuple/fail_implicit_convert_with_as.carbon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ external impl A as As(B) {
2121

2222
fn Main() -> i32 {
2323
var a: (i32, A) = (1, {.a = 2});
24-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_implicit_convert_with_as.carbon:[[@LINE+1]]: type error in name binding: '(i32, class A)' is not implicitly convertible to '(i32, class B)'
24+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_implicit_convert_with_as.carbon:[[@LINE+1]]: type error in initializer of variable: '(i32, class A)' is not implicitly convertible to '(i32, class B)'
2525
var b: (i32, B) = a;
26-
return 0;
26+
return b[1].b;
2727
}

explorer/testdata/tuple/fail_to_array.carbon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ package ExplorerTest api;
1010

1111
fn Main() -> i32 {
1212
var t: auto = (1, 2);
13-
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_to_array.carbon:[[@LINE+1]]: type error in name binding: '(i32, i32)' is not implicitly convertible to '[i32; 3]'
13+
// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_to_array.carbon:[[@LINE+1]]: type error in initializer of variable: '(i32, i32)' is not implicitly convertible to '[i32; 3]'
1414
var a: [i32; 3] = t;
1515
}

0 commit comments

Comments
 (0)