From a3329cf004cd355deebac720904e6e1c93f0b14a Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 13 Oct 2022 15:30:29 -0700 Subject: [PATCH] Diagnose uses of declarations that are too early. (#2288) There are lots of ways a declaration can be used before we have the information necessary to handle that use. Issue diagnostics for these. Interleave declaration and type-checking of global declarations so that declaring a later declaration can depend on the results of type-checking an earlier one. Incorporates tests added in #2266. Fixes #1394, fixes #1395, fixes #1396. Co-authored-by: pmqtt <51272730+pmqtt@users.noreply.github.com> Co-authored-by: Jon Ross-Perkins --- bazel/testing/lit_autoupdate_base.py | 6 +- explorer/ast/declaration.h | 22 ++ explorer/interpreter/interpreter.cpp | 19 +- explorer/interpreter/type_checker.cpp | 223 ++++++++++++++---- explorer/interpreter/type_checker.h | 12 +- .../fail_multi_impl_scoping.carbon | 7 +- .../testdata/auto/fail_use_in_init.carbon | 16 ++ .../testdata/choice/fail_recursive_use.carbon | 19 ++ .../fail_member_call_before_typecheck.carbon | 21 ++ .../testdata/class/fail_member_of_self.carbon | 18 ++ .../class/fail_use_before_typecheck.carbon | 19 ++ .../function/fail_call_undefined.carbon | 16 ++ .../function/fail_call_undefined_main.carbon | 12 + .../function/fail_invalid_fnty.carbon | 4 +- .../fail_recurse_before_typecheck.carbon | 19 ++ .../fail_recurse_in_return_type.carbon | 16 ++ .../fail_return_call_has_invalid_body.carbon | 19 ++ .../function/fail_var_type_is_call.carbon | 18 ++ .../fail_modify_in_constant_expr.carbon | 23 ++ .../fail_member_lookup_in_definition.carbon | 19 ++ .../fail_self_lookup_in_definition.carbon | 19 ++ .../mixin/fail_recursive_mixing.carbon | 2 +- 22 files changed, 481 insertions(+), 68 deletions(-) create mode 100644 explorer/testdata/auto/fail_use_in_init.carbon create mode 100644 explorer/testdata/choice/fail_recursive_use.carbon create mode 100644 explorer/testdata/class/fail_member_call_before_typecheck.carbon create mode 100644 explorer/testdata/class/fail_member_of_self.carbon create mode 100644 explorer/testdata/class/fail_use_before_typecheck.carbon create mode 100644 explorer/testdata/function/fail_call_undefined.carbon create mode 100644 explorer/testdata/function/fail_call_undefined_main.carbon create mode 100644 explorer/testdata/function/fail_recurse_before_typecheck.carbon create mode 100644 explorer/testdata/function/fail_recurse_in_return_type.carbon create mode 100644 explorer/testdata/function/fail_return_call_has_invalid_body.carbon create mode 100644 explorer/testdata/function/fail_var_type_is_call.carbon create mode 100644 explorer/testdata/global_variable/fail_modify_in_constant_expr.carbon create mode 100644 explorer/testdata/interface/fail_member_lookup_in_definition.carbon create mode 100644 explorer/testdata/interface/fail_self_lookup_in_definition.carbon diff --git a/bazel/testing/lit_autoupdate_base.py b/bazel/testing/lit_autoupdate_base.py index 7ebf0fd39587..6e4f5fcd28d7 100755 --- a/bazel/testing/lit_autoupdate_base.py +++ b/bazel/testing/lit_autoupdate_base.py @@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from abc import ABC, abstractmethod import argparse from concurrent import futures +import logging import os from pathlib import Path import re @@ -366,7 +367,10 @@ def update_checks(parsed_args: ParsedArgs, tests: Set[Path]) -> None: """Updates CHECK: lines in lit tests.""" def map_helper(test: Path) -> bool: - updated = update_check(parsed_args, test) + try: + updated = update_check(parsed_args, test) + except Exception: + logging.exception(f"Failed to update {test}") print(".", end="", flush=True) return updated diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index 3f909e293017..b53d1e8f9e0f 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -90,6 +90,26 @@ class Declaration : public AstNode { return constant_value_; } + // Returns whether this node has been declared. + auto is_declared() const -> bool { return is_declared_; } + + // Set that this node is declared. Should only be called once, by the + // type-checker, once the node is ready to be named and used. + void set_is_declared() { + CARBON_CHECK(!is_declared_) << "should not be declared twice"; + is_declared_ = true; + } + + // Returns whether this node has been fully type-checked. + auto is_type_checked() const -> bool { return is_type_checked_; } + + // Set that this node is type-checked. Should only be called once, by the + // type-checker, once full type-checking is complete. + void set_is_type_checked() { + CARBON_CHECK(!is_type_checked_) << "should not be type-checked twice"; + is_type_checked_ = true; + } + protected: // Constructs a Declaration representing syntax at the given line number. // `kind` must be the enumerator corresponding to the most-derived type being @@ -100,6 +120,8 @@ class Declaration : public AstNode { private: std::optional> static_type_; std::optional> constant_value_; + bool is_declared_ = false; + bool is_type_checked_ = false; }; class CallableDeclaration : public Declaration { diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 2f18230cb003..5696e2c2bd1e 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -592,6 +592,13 @@ auto Interpreter::InstantiateType(Nonnull type, InstantiateBindings(&class_type.bindings(), source_loc)); return arena_->New(&class_type.declaration(), bindings); } + case Value::Kind::ChoiceType: { + const auto& choice_type = cast(*type); + CARBON_ASSIGN_OR_RETURN( + Nonnull bindings, + InstantiateBindings(&choice_type.bindings(), source_loc)); + return arena_->New(&choice_type.declaration(), bindings); + } case Value::Kind::AssociatedConstant: { CARBON_ASSIGN_OR_RETURN( Nonnull type_value, @@ -801,6 +808,16 @@ auto Interpreter::CallFunction(const CallExpression& call, case Value::Kind::FunctionValue: { const FunctionValue& fun_val = cast(*fun); const FunctionDeclaration& function = fun_val.declaration(); + if (!function.body().has_value()) { + return ProgramError(call.source_loc()) + << "attempt to call function `" << function.name() + << "` that has not been defined"; + } + if (!function.is_type_checked()) { + return ProgramError(call.source_loc()) + << "attempt to call function `" << function.name() + << "` that has not been fully type-checked"; + } RuntimeScope binding_scope(&heap_); // Bring the class type arguments into scope. for (const auto& [bind, val] : fun_val.type_args()) { @@ -830,8 +847,6 @@ auto Interpreter::CallFunction(const CallExpression& call, CARBON_CHECK(PatternMatch( &function.param_pattern().value(), converted_args, call.source_loc(), &function_scope, generic_args, trace_stream_, this->arena_)); - CARBON_CHECK(function.body().has_value()) - << "Calling a function that's missing a body"; return todo_.Spawn(std::make_unique(*function.body()), std::move(function_scope)); } diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 81356c3f6a03..e17dd28a031e 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -282,8 +282,7 @@ static auto IsType(Nonnull value, bool concrete = false) -> bool { } } -auto TypeChecker::ExpectIsType(SourceLocation source_loc, - Nonnull value) +static auto ExpectIsType(SourceLocation source_loc, Nonnull value) -> ErrorOr { if (!IsType(value)) { return ProgramError(source_loc) << "Expected a type, but got " << *value; @@ -292,6 +291,102 @@ auto TypeChecker::ExpectIsType(SourceLocation source_loc, } } +// Expect that a type is complete. Issue a diagnostic if not. +static auto ExpectCompleteType(SourceLocation source_loc, + std::string_view context, + Nonnull type) -> ErrorOr { + CARBON_RETURN_IF_ERROR(ExpectIsType(source_loc, type)); + + switch (type->kind()) { + case Value::Kind::IntValue: + case Value::Kind::FunctionValue: + case Value::Kind::DestructorValue: + case Value::Kind::BoundMethodValue: + case Value::Kind::PointerValue: + case Value::Kind::LValue: + case Value::Kind::BoolValue: + case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: + case Value::Kind::AlternativeValue: + case Value::Kind::BindingPlaceholderValue: + case Value::Kind::AddrValue: + case Value::Kind::AlternativeConstructorValue: + case Value::Kind::ContinuationValue: + case Value::Kind::StringValue: + case Value::Kind::UninitializedValue: + case Value::Kind::ImplWitness: + case Value::Kind::BindingWitness: + case Value::Kind::ConstraintWitness: + case Value::Kind::ConstraintImplWitness: + case Value::Kind::ParameterizedEntityName: + case Value::Kind::MemberName: + case Value::Kind::TypeOfParameterizedEntityName: + case Value::Kind::TypeOfMemberName: + case Value::Kind::MixinPseudoType: + case Value::Kind::TypeOfMixinPseudoType: + CARBON_FATAL() << "should not see non-type values"; + + case Value::Kind::IntType: + case Value::Kind::BoolType: + case Value::Kind::StringType: + case Value::Kind::PointerType: + case Value::Kind::TypeOfClassType: + case Value::Kind::TypeOfInterfaceType: + case Value::Kind::TypeOfConstraintType: + case Value::Kind::TypeOfChoiceType: + case Value::Kind::TypeType: + case Value::Kind::FunctionType: + case Value::Kind::StructType: + case Value::Kind::ConstraintType: + case Value::Kind::ContinuationType: + case Value::Kind::VariableType: + case Value::Kind::AssociatedConstant: { + // These types are always complete. + return Success(); + } + + case Value::Kind::StaticArrayType: + // TODO: This should probably be complete only if the element type is + // complete. + return Success(); + + case Value::Kind::TupleValue: { + // TODO: Tuple types should be complete only if all element types are + // complete. + return Success(); + } + + // TODO: Once we support forward-declarations, make sure we have an actual + // definition in these cases. + case Value::Kind::NominalClassType: { + if (cast(type)->declaration().is_declared()) { + return Success(); + } + break; + } + case Value::Kind::InterfaceType: { + if (cast(type)->declaration().is_declared()) { + return Success(); + } + break; + } + case Value::Kind::ChoiceType: { + if (cast(type)->declaration().is_declared()) { + return Success(); + } + break; + } + + case Value::Kind::AutoType: { + // Undeduced `auto` is considered incomplete. + break; + } + } + + return ProgramError(source_loc) + << "incomplete type `" << *type << "` used in " << context; +} + // Returns whether *value represents the type of a Carbon value, as // opposed to a type pattern or a non-type value. static auto IsConcreteType(Nonnull value) -> bool { @@ -1702,11 +1797,12 @@ auto TypeChecker::MakeConstraintWitnessAccess(Nonnull witness, auto TypeChecker::MakeConstraintForInterface( SourceLocation source_loc, Nonnull iface_type) -> ErrorOr> { + CARBON_RETURN_IF_ERROR( + ExpectCompleteType(source_loc, "constraint", iface_type)); + auto constraint_type = iface_type->declaration().constraint_type(); - if (!constraint_type) { - return ProgramError(source_loc) - << "use of " << *iface_type << " before it is completely defined"; - } + CARBON_CHECK(constraint_type) + << "complete interface should have a constraint type"; if (iface_type->bindings().empty()) { return *constraint_type; @@ -2065,6 +2161,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, auto& access = cast(*e); CARBON_RETURN_IF_ERROR(TypeCheckExp(&access.object(), impl_scope)); const Value& object_type = access.object().static_type(); + CARBON_RETURN_IF_ERROR(ExpectCompleteType(access.source_loc(), + "member access", &object_type)); switch (object_type.kind()) { case Value::Kind::StructType: { const auto& struct_type = cast(object_type); @@ -2082,10 +2180,11 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case Value::Kind::NominalClassType: { const auto& t_class = cast(object_type); - if (auto type_member = FindMixedMemberAndType( - access.member_name(), t_class.declaration().members(), - &t_class); - type_member.has_value()) { + CARBON_ASSIGN_OR_RETURN( + auto type_member, FindMixedMemberAndType( + access.source_loc(), access.member_name(), + t_class.declaration().members(), &t_class)); + if (type_member.has_value()) { auto [member_type, member] = type_member.value(); Nonnull field_type = Substitute(t_class.bindings(), member_type); @@ -2258,6 +2357,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, CARBON_ASSIGN_OR_RETURN( Nonnull type, InterpExp(&access.object(), arena_, trace_stream_)); + CARBON_RETURN_IF_ERROR( + ExpectCompleteType(access.source_loc(), "member access", type)); switch (type->kind()) { case Value::Kind::StructType: { for (const auto& field : cast(type)->fields()) { @@ -2302,10 +2403,12 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case Value::Kind::NominalClassType: { const NominalClassType& class_type = cast(*type); - if (auto type_member = FindMixedMemberAndType( - access.member_name(), class_type.declaration().members(), - &class_type); - type_member.has_value()) { + CARBON_ASSIGN_OR_RETURN( + auto type_member, + FindMixedMemberAndType( + access.source_loc(), access.member_name(), + class_type.declaration().members(), &class_type)); + if (type_member.has_value()) { auto [member_type, member] = type_member.value(); access.set_member(Member(member)); switch (member->kind()) { @@ -3622,28 +3725,35 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, } case StatementKind::VariableDefinition: { auto& var = cast(*s); + + // TODO: If the pattern contains a binding that implies a new impl is + // available, should that remain in scope for as long as its binding? + // ``` + // var a: (T:! Widget) = ...; + // // Is the `impl T as Widget` in scope here? + // a.(Widget.F)(); + // ``` ImplScope var_scope; var_scope.AddParent(&impl_scope); + std::optional> init_type; + + // Type-check the initializer before we inspect the type of the variable + // so we can use its type to deduce parts of the type of the binding. if (var.has_init()) { CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope)); - const Value& rhs_ty = var.init().static_type(); - // TODO: If the pattern contains a binding that implies a new impl is - // available, should that remain in scope for as long as its binding? - // ``` - // var a: (T:! Widget) = ...; - // // Is the `impl T as Widget` in scope here? - // a.(Widget.F)(); - // ``` - CARBON_RETURN_IF_ERROR(TypeCheckPattern( - &var.pattern(), &rhs_ty, var_scope, var.value_category())); + init_type = &var.init().static_type(); + } + CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), init_type, + var_scope, var.value_category())); + CARBON_RETURN_IF_ERROR(ExpectCompleteType( + var.source_loc(), "type of variable", &var.pattern().static_type())); + + if (var.has_init()) { CARBON_ASSIGN_OR_RETURN( Nonnull converted_init, ImplicitlyConvert("initializer of variable", impl_scope, &var.init(), &var.pattern().static_type())); var.set_init(converted_init); - } else { - CARBON_RETURN_IF_ERROR(TypeCheckPattern( - &var.pattern(), std::nullopt, var_scope, var.value_category())); } return Success(); } @@ -3926,7 +4036,7 @@ auto TypeChecker::TypeCheckCallableDeclaration(Nonnull f, if (trace_stream_) { **trace_stream_ << "** checking function " << f->name() << "\n"; } - // if f->return_term().is_auto(), the function body was already + // If f->return_term().is_auto(), the function body was already // type checked in DeclareFunctionDeclaration. if (f->body().has_value() && !f->return_term().is_auto()) { // Bring the impls into scope. @@ -4452,6 +4562,8 @@ auto TypeChecker::CheckAndAddImplBindings( Nonnull constraint = impl_decl->constraint_type(); for (auto lookup : constraint->lookup_contexts()) { if (auto* iface_type = dyn_cast(lookup.context)) { + CARBON_RETURN_IF_ERROR(ExpectCompleteType( + impl_decl->source_loc(), "impl declaration", iface_type)); CARBON_RETURN_IF_ERROR( CheckImplIsDeducible(impl_decl->source_loc(), impl_type, iface_type, deduced_bindings, *scope_info.innermost_scope)); @@ -4790,13 +4902,11 @@ auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr { for (Nonnull declaration : ast.declarations) { CARBON_RETURN_IF_ERROR( DeclareDeclaration(declaration, top_level_scope_info)); - } - for (Nonnull decl : ast.declarations) { CARBON_RETURN_IF_ERROR( - TypeCheckDeclaration(decl, impl_scope, std::nullopt)); + TypeCheckDeclaration(declaration, impl_scope, std::nullopt)); // Check to see if this declaration is a builtin. // TODO: Only do this when type-checking the prelude. - builtins_.Register(decl); + builtins_.Register(declaration); } CARBON_RETURN_IF_ERROR(TypeCheckExp(*ast.main_call, impl_scope)); return Success(); @@ -4824,25 +4934,25 @@ auto TypeChecker::TypeCheckDeclaration( case DeclarationKind::FunctionDeclaration: CARBON_RETURN_IF_ERROR(TypeCheckCallableDeclaration( &cast(*d), impl_scope)); - return Success(); + break; case DeclarationKind::ClassDeclaration: CARBON_RETURN_IF_ERROR( TypeCheckClassDeclaration(&cast(*d), impl_scope)); - return Success(); + break; case DeclarationKind::MixinDeclaration: { CARBON_RETURN_IF_ERROR( TypeCheckMixinDeclaration(&cast(*d), impl_scope)); - return Success(); + break; } case DeclarationKind::MixDeclaration: { CARBON_RETURN_IF_ERROR(TypeCheckMixDeclaration( &cast(*d), impl_scope, enclosing_decl)); - return Success(); + break; } case DeclarationKind::ChoiceDeclaration: CARBON_RETURN_IF_ERROR( TypeCheckChoiceDeclaration(&cast(*d), impl_scope)); - return Success(); + break; case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); if (var.has_initializer()) { @@ -4862,25 +4972,26 @@ auto TypeChecker::TypeCheckDeclaration( &var.initializer(), &var.static_type())); var.set_initializer(converted_initializer); } - return Success(); + break; } case DeclarationKind::InterfaceExtendsDeclaration: { // Checked in DeclareInterfaceDeclaration. - return Success(); + break; } case DeclarationKind::InterfaceImplDeclaration: { // Checked in DeclareInterfaceDeclaration. - return Success(); + break; } case DeclarationKind::AssociatedConstantDeclaration: - return Success(); + break; case DeclarationKind::SelfDeclaration: { CARBON_FATAL() << "Unreachable TypeChecker `Self` declaration"; } case DeclarationKind::AliasDeclaration: { - return Success(); + break; } } + d->set_is_type_checked(); return Success(); } @@ -4926,6 +5037,12 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, Nonnull mixin, InterpExp(&mix_decl.mixin(), arena_, trace_stream_)); mix_decl.set_mixin_value(cast(mixin)); + auto& mixin_decl = mix_decl.mixin_value().declaration(); + if (!mixin_decl.is_declared()) { + return ProgramError(mix_decl.source_loc()) + << "incomplete mixin `" << mixin_decl.name() + << "` used in mix declaration"; + } break; } case DeclarationKind::ChoiceDeclaration: { @@ -4949,6 +5066,8 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, var.value_category())); CARBON_ASSIGN_OR_RETURN(Nonnull declared_type, InterpExp(&type, arena_, trace_stream_)); + CARBON_RETURN_IF_ERROR(ExpectCompleteType( + var.source_loc(), "type of variable", declared_type)); var.set_static_type(declared_type); break; } @@ -4984,20 +5103,24 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, break; } } + d->set_is_declared(); return Success(); } auto TypeChecker::FindMixedMemberAndType( - const std::string_view& name, llvm::ArrayRef> members, + SourceLocation source_loc, const std::string_view& name, + llvm::ArrayRef> members, const Nonnull enclosing_type) - -> std::optional< - std::pair, Nonnull>> { + -> ErrorOr, Nonnull>>> { for (Nonnull member : members) { if (llvm::isa(member)) { const auto& mix_decl = cast(*member); Nonnull mixin = &mix_decl.mixin_value(); - const auto res = - FindMixedMemberAndType(name, mixin->declaration().members(), mixin); + CARBON_ASSIGN_OR_RETURN( + const auto res, + FindMixedMemberAndType(source_loc, name, + mixin->declaration().members(), mixin)); if (res.has_value()) { if (isa(enclosing_type)) { Bindings temp_map; @@ -5005,7 +5128,7 @@ auto TypeChecker::FindMixedMemberAndType( temp_map.Add(mixin->declaration().self(), enclosing_type, std::nullopt); const auto mix_member_type = Substitute(temp_map, res.value().first); - return std::make_pair(mix_member_type, res.value().second); + return {std::make_pair(mix_member_type, res.value().second)}; } else { return res; } @@ -5014,12 +5137,12 @@ auto TypeChecker::FindMixedMemberAndType( } else if (std::optional mem_name = GetName(*member); mem_name.has_value()) { if (*mem_name == name) { - return std::make_pair(&member->static_type(), member); + return {std::make_pair(&member->static_type(), member)}; } } } - return std::nullopt; + return {std::nullopt}; } auto TypeChecker::CollectMember(Nonnull enclosing_decl, diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 11f78848d92d..46ba32657b61 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -59,11 +59,12 @@ class TypeChecker { ** the member's type is substituted with the type of the enclosing declaration ** containing the mix declaration. */ - auto FindMixedMemberAndType(const std::string_view& name, + auto FindMixedMemberAndType(SourceLocation source_loc, + const std::string_view& name, llvm::ArrayRef> members, const Nonnull enclosing_type) - -> std::optional< - std::pair, Nonnull>>; + -> ErrorOr, Nonnull>>>; // Given the witnesses for the components of a constraint, form a witness for // the constraint. @@ -315,11 +316,6 @@ class TypeChecker { auto ExpectReturnOnAllPaths(std::optional> opt_stmt, SourceLocation source_loc) -> ErrorOr; - // Verifies that *value represents the result of a type expression, - // as opposed to a non-type value. - auto ExpectIsType(SourceLocation source_loc, Nonnull value) - -> ErrorOr; - // Verifies that *value represents a concrete type, as opposed to a // type pattern or a non-type value. auto ExpectIsConcreteType(SourceLocation source_loc, diff --git a/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon b/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon index 61071827db91..59c5ac4f58bb 100644 --- a/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon +++ b/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon @@ -28,9 +28,11 @@ class C(T:! Type) { fn FB() -> i32 { // OK, know that TB is i32 here. let v: Self.(B.TB) = 2; - // TODO: Don't know that TA is i32. It could be specialized. - // We should reject this once we support specialization. + // Don't know that TA is i32; it could be specialized. let w: Self.(A.TA) = 3; + // TODO: This error is confusing. We should be diagnosing the previous + // line because we don't know that `3` can be converted to `Self.(A.TA)`. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon:[[@LINE+1]]: type error in return value: '((class C(T = T)).TB).Result' is not implicitly convertible to 'i32' return v + w; } } @@ -38,7 +40,6 @@ class C(T:! Type) { external impl C(i32) as B where .TB == () { fn FB() -> () { return (); } -// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/assoc_const/fail_multi_impl_scoping.carbon:[[@LINE+1]]: ambiguous implementations of interface B for class C(T = i32) } fn Main() -> i32 { return C(i32).FB(); } diff --git a/explorer/testdata/auto/fail_use_in_init.carbon b/explorer/testdata/auto/fail_use_in_init.carbon new file mode 100644 index 000000000000..782c955f19ed --- /dev/null +++ b/explorer/testdata/auto/fail_use_in_init.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +fn Main() -> i32 { + // TODO: The error here should be that we aren't allowed to refer to 'a' yet. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/auto/fail_use_in_init.carbon:[[@LINE+1]]: could not resolve 'a' + var a: auto = a; + return 0; +} diff --git a/explorer/testdata/choice/fail_recursive_use.carbon b/explorer/testdata/choice/fail_recursive_use.carbon new file mode 100644 index 000000000000..ff22a1b9093f --- /dev/null +++ b/explorer/testdata/choice/fail_recursive_use.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +choice C { + X, + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/choice/fail_recursive_use.carbon:[[@LINE+1]]: 'C' is not usable until after it has been completely declared + Y(if C.X then i32 else i32) +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/class/fail_member_call_before_typecheck.carbon b/explorer/testdata/class/fail_member_call_before_typecheck.carbon new file mode 100644 index 000000000000..dd20c4943010 --- /dev/null +++ b/explorer/testdata/class/fail_member_call_before_typecheck.carbon @@ -0,0 +1,21 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +class C { + fn PickType() -> Type { return i32; } + // This is invalid even though `PickType` is defined earlier, because + // checking of member bodies is deferred until after the class is completed. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_member_call_before_typecheck.carbon:[[@LINE+1]]: attempt to call function `PickType` that has not been fully type-checked + fn GetZero() -> PickType() { return 0; } +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/class/fail_member_of_self.carbon b/explorer/testdata/class/fail_member_of_self.carbon new file mode 100644 index 000000000000..abbef883c0d5 --- /dev/null +++ b/explorer/testdata/class/fail_member_of_self.carbon @@ -0,0 +1,18 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +class C { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_member_of_self.carbon:[[@LINE+1]]: incomplete type `class C` used in type of variable + var m: Self; +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/class/fail_use_before_typecheck.carbon b/explorer/testdata/class/fail_use_before_typecheck.carbon new file mode 100644 index 000000000000..500117bcfc73 --- /dev/null +++ b/explorer/testdata/class/fail_use_before_typecheck.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +class C { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_use_before_typecheck.carbon:[[@LINE+1]]: incomplete type `class C` used in member access + var n: if not C.WrapInStruct() then i32 else {.n: i32}; + fn WrapInStruct() -> bool { return true; } +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/function/fail_call_undefined.carbon b/explorer/testdata/function/fail_call_undefined.carbon new file mode 100644 index 000000000000..5dfae2d3c527 --- /dev/null +++ b/explorer/testdata/function/fail_call_undefined.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +fn F() -> i32; + +fn Main() -> i32 { + // CHECK:STDERR: RUNTIME ERROR: {{.*}}/explorer/testdata/function/fail_call_undefined.carbon:[[@LINE+1]]: attempt to call function `F` that has not been defined + return F(); +} diff --git a/explorer/testdata/function/fail_call_undefined_main.carbon b/explorer/testdata/function/fail_call_undefined_main.carbon new file mode 100644 index 000000000000..95072c9e5c08 --- /dev/null +++ b/explorer/testdata/function/fail_call_undefined_main.carbon @@ -0,0 +1,12 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s +// CHECK:STDERR: RUNTIME ERROR: :0: attempt to call function `Main` that has not been defined + +package ExplorerTest api; + +fn Main() -> i32; diff --git a/explorer/testdata/function/fail_invalid_fnty.carbon b/explorer/testdata/function/fail_invalid_fnty.carbon index a9a6450770da..44df85be7a27 100644 --- a/explorer/testdata/function/fail_invalid_fnty.carbon +++ b/explorer/testdata/function/fail_invalid_fnty.carbon @@ -8,10 +8,8 @@ package ExplorerTest api; -// TODO: We should type-check the prelude before the main input so that we can -// use it within the types of top-level declarations. // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_invalid_fnty.carbon:[[@LINE+2]]: type error in `-`: -// CHECK:STDERR: missing declaration for builtin `Negate` +// CHECK:STDERR: could not find implementation of interface Negate for bool fn f(g: __Fn(-true) -> true) { } diff --git a/explorer/testdata/function/fail_recurse_before_typecheck.carbon b/explorer/testdata/function/fail_recurse_before_typecheck.carbon new file mode 100644 index 000000000000..d78facad6864 --- /dev/null +++ b/explorer/testdata/function/fail_recurse_before_typecheck.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +fn T() -> Type { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_recurse_before_typecheck.carbon:[[@LINE+1]]: attempt to call function `T` that has not been fully type-checked + var v: T() = 0; + return i32; +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/function/fail_recurse_in_return_type.carbon b/explorer/testdata/function/fail_recurse_in_return_type.carbon new file mode 100644 index 000000000000..d098f8a96350 --- /dev/null +++ b/explorer/testdata/function/fail_recurse_in_return_type.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_recurse_in_return_type.carbon:[[@LINE+1]]: 'T' is not usable until after it has been completely declared +fn T() -> T() { return i32; } + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/function/fail_return_call_has_invalid_body.carbon b/explorer/testdata/function/fail_return_call_has_invalid_body.carbon new file mode 100644 index 000000000000..7a57201a6df4 --- /dev/null +++ b/explorer/testdata/function/fail_return_call_has_invalid_body.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 2>&1 | %{FileCheck} %s +// AUTOUPDATE: %{explorer} %s + +package EmptyIdentifier impl; + +fn apply[T:! Type, U:! Type](f: T, EmptyIdentifier: U) { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_return_call_has_invalid_body.carbon:[[@LINE+1]]: expected a tuple + match (true[true]) {} +} + +fn EmptyIdentifier() -> apply(true, true); + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/function/fail_var_type_is_call.carbon b/explorer/testdata/function/fail_var_type_is_call.carbon new file mode 100644 index 000000000000..1b73eb841bb1 --- /dev/null +++ b/explorer/testdata/function/fail_var_type_is_call.carbon @@ -0,0 +1,18 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 2>&1 | %{FileCheck} %s +// AUTOUPDATE: %{explorer} %s + +package EmptyIdentifier impl; + +fn test() -> i32 { + return 1; +} + +fn Main() -> i32 { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_var_type_is_call.carbon:[[@LINE+1]]: Expected a type, but got 1 + var x: test() = 1; + return 0; +} diff --git a/explorer/testdata/global_variable/fail_modify_in_constant_expr.carbon b/explorer/testdata/global_variable/fail_modify_in_constant_expr.carbon new file mode 100644 index 000000000000..7b224c95778d --- /dev/null +++ b/explorer/testdata/global_variable/fail_modify_in_constant_expr.carbon @@ -0,0 +1,23 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +var n: i32 = 0; + +fn F() -> Type { + // TODO: This isn't a very good description of the problem, which is that + // compile-time evaluation doesn't have a mutable `n` value available. + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/global_variable/fail_modify_in_constant_expr.carbon:[[@LINE+1]]: could not find `n: i32` + n = 1; + return i32; +} + +fn Main() -> F() { + return n; +} diff --git a/explorer/testdata/interface/fail_member_lookup_in_definition.carbon b/explorer/testdata/interface/fail_member_lookup_in_definition.carbon new file mode 100644 index 000000000000..cf080f2ed9ad --- /dev/null +++ b/explorer/testdata/interface/fail_member_lookup_in_definition.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface Vector { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_member_lookup_in_definition.carbon:[[@LINE+1]]: incomplete type `interface Vector` used in member access + fn ScalarZero() -> Vector.ScalarType; + let ScalarType:! Type; +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/interface/fail_self_lookup_in_definition.carbon b/explorer/testdata/interface/fail_self_lookup_in_definition.carbon new file mode 100644 index 000000000000..45e18e3969fc --- /dev/null +++ b/explorer/testdata/interface/fail_self_lookup_in_definition.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{not} %{explorer} %s 2>&1 | %{FileCheck-strict} %s +// RUN: %{not} %{explorer-trace} %s 2>&1 | %{FileCheck-allow-unmatched} %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +interface Vector { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_self_lookup_in_definition.carbon:[[@LINE+1]]: incomplete type `interface Vector` used in member access + fn ScalarZero() -> Self.ScalarType; + let ScalarType:! Type; +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/mixin/fail_recursive_mixing.carbon b/explorer/testdata/mixin/fail_recursive_mixing.carbon index d76e8c4f3455..91bffcc5168f 100644 --- a/explorer/testdata/mixin/fail_recursive_mixing.carbon +++ b/explorer/testdata/mixin/fail_recursive_mixing.carbon @@ -11,8 +11,8 @@ __mixin M1 { fn F1[me: Self](x: Self) -> Self{ return x; } + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail_recursive_mixing.carbon:[[@LINE+1]]: incomplete mixin `M1` used in mix declaration __mix M1; -// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail_recursive_mixing.carbon:[[@LINE+1]]: Member named F1 (declared at {{.*}}/explorer/testdata/mixin/fail_recursive_mixing.carbon:[[@LINE-2]]) is being mixed multiple times into M1 } class C {