Przeglądaj źródła

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 <jperkins@google.com>
Richard Smith 3 lat temu
rodzic
commit
a3329cf004

+ 5 - 1
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
 

+ 22 - 0
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<Nonnull<const Value*>> static_type_;
   std::optional<Nonnull<const Value*>> constant_value_;
+  bool is_declared_ = false;
+  bool is_type_checked_ = false;
 };
 
 class CallableDeclaration : public Declaration {

+ 17 - 2
explorer/interpreter/interpreter.cpp

@@ -592,6 +592,13 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
           InstantiateBindings(&class_type.bindings(), source_loc));
       return arena_->New<NominalClassType>(&class_type.declaration(), bindings);
     }
+    case Value::Kind::ChoiceType: {
+      const auto& choice_type = cast<ChoiceType>(*type);
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Bindings*> bindings,
+          InstantiateBindings(&choice_type.bindings(), source_loc));
+      return arena_->New<ChoiceType>(&choice_type.declaration(), bindings);
+    }
     case Value::Kind::AssociatedConstant: {
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Value*> type_value,
@@ -801,6 +808,16 @@ auto Interpreter::CallFunction(const CallExpression& call,
     case Value::Kind::FunctionValue: {
       const FunctionValue& fun_val = cast<FunctionValue>(*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<StatementAction>(*function.body()),
                          std::move(function_scope));
     }

+ 173 - 50
explorer/interpreter/type_checker.cpp

@@ -282,8 +282,7 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
   }
 }
 
-auto TypeChecker::ExpectIsType(SourceLocation source_loc,
-                               Nonnull<const Value*> value)
+static auto ExpectIsType(SourceLocation source_loc, Nonnull<const Value*> value)
     -> ErrorOr<Success> {
   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<const Value*> type) -> ErrorOr<Success> {
+  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<NominalClassType>(type)->declaration().is_declared()) {
+        return Success();
+      }
+      break;
+    }
+    case Value::Kind::InterfaceType: {
+      if (cast<InterfaceType>(type)->declaration().is_declared()) {
+        return Success();
+      }
+      break;
+    }
+    case Value::Kind::ChoiceType: {
+      if (cast<ChoiceType>(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<const Value*> value) -> bool {
@@ -1702,11 +1797,12 @@ auto TypeChecker::MakeConstraintWitnessAccess(Nonnull<const Witness*> witness,
 auto TypeChecker::MakeConstraintForInterface(
     SourceLocation source_loc, Nonnull<const InterfaceType*> iface_type)
     -> ErrorOr<Nonnull<const ConstraintType*>> {
+  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<Expression*> e,
       auto& access = cast<SimpleMemberAccessExpression>(*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<StructType>(object_type);
@@ -2082,10 +2180,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         }
         case Value::Kind::NominalClassType: {
           const auto& t_class = cast<NominalClassType>(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<const Value*> field_type =
                 Substitute(t_class.bindings(), member_type);
@@ -2258,6 +2357,8 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           CARBON_ASSIGN_OR_RETURN(
               Nonnull<const Value*> 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<StructType>(type)->fields()) {
@@ -2302,10 +2403,12 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
             case Value::Kind::NominalClassType: {
               const NominalClassType& class_type =
                   cast<NominalClassType>(*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<Statement*> s,
     }
     case StatementKind::VariableDefinition: {
       auto& var = cast<VariableDefinition>(*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<Nonnull<const Value*>> 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<Expression*> 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<CallableDeclaration*> 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<const ConstraintType*> constraint = impl_decl->constraint_type();
   for (auto lookup : constraint->lookup_contexts()) {
     if (auto* iface_type = dyn_cast<InterfaceType>(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<Success> {
   for (Nonnull<Declaration*> declaration : ast.declarations) {
     CARBON_RETURN_IF_ERROR(
         DeclareDeclaration(declaration, top_level_scope_info));
-  }
-  for (Nonnull<Declaration*> 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<CallableDeclaration>(*d), impl_scope));
-      return Success();
+      break;
     case DeclarationKind::ClassDeclaration:
       CARBON_RETURN_IF_ERROR(
           TypeCheckClassDeclaration(&cast<ClassDeclaration>(*d), impl_scope));
-      return Success();
+      break;
     case DeclarationKind::MixinDeclaration: {
       CARBON_RETURN_IF_ERROR(
           TypeCheckMixinDeclaration(&cast<MixinDeclaration>(*d), impl_scope));
-      return Success();
+      break;
     }
     case DeclarationKind::MixDeclaration: {
       CARBON_RETURN_IF_ERROR(TypeCheckMixDeclaration(
           &cast<MixDeclaration>(*d), impl_scope, enclosing_decl));
-      return Success();
+      break;
     }
     case DeclarationKind::ChoiceDeclaration:
       CARBON_RETURN_IF_ERROR(
           TypeCheckChoiceDeclaration(&cast<ChoiceDeclaration>(*d), impl_scope));
-      return Success();
+      break;
     case DeclarationKind::VariableDeclaration: {
       auto& var = cast<VariableDeclaration>(*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<Declaration*> d,
           Nonnull<const Value*> mixin,
           InterpExp(&mix_decl.mixin(), arena_, trace_stream_));
       mix_decl.set_mixin_value(cast<MixinPseudoType>(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<Declaration*> d,
                                               var.value_category()));
       CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> 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<Declaration*> d,
       break;
     }
   }
+  d->set_is_declared();
   return Success();
 }
 
 auto TypeChecker::FindMixedMemberAndType(
-    const std::string_view& name, llvm::ArrayRef<Nonnull<Declaration*>> members,
+    SourceLocation source_loc, const std::string_view& name,
+    llvm::ArrayRef<Nonnull<Declaration*>> members,
     const Nonnull<const Value*> enclosing_type)
-    -> std::optional<
-        std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>> {
+    -> ErrorOr<std::optional<
+        std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>> {
   for (Nonnull<const Declaration*> member : members) {
     if (llvm::isa<MixDeclaration>(member)) {
       const auto& mix_decl = cast<MixDeclaration>(*member);
       Nonnull<const MixinPseudoType*> 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<NominalClassType>(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<std::string_view> 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<const Declaration*> enclosing_decl,

+ 4 - 8
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<Nonnull<Declaration*>> members,
                               const Nonnull<const Value*> enclosing_type)
-      -> std::optional<
-          std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>;
+      -> ErrorOr<std::optional<
+          std::pair<Nonnull<const Value*>, Nonnull<const Declaration*>>>>;
 
   // 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<Nonnull<Statement*>> opt_stmt,
                               SourceLocation source_loc) -> ErrorOr<Success>;
 
-  // Verifies that *value represents the result of a type expression,
-  // as opposed to a non-type value.
-  auto ExpectIsType(SourceLocation source_loc, Nonnull<const Value*> value)
-      -> ErrorOr<Success>;
-
   // Verifies that *value represents a concrete type, as opposed to a
   // type pattern or a non-type value.
   auto ExpectIsConcreteType(SourceLocation source_loc,

+ 4 - 3
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(); }

+ 16 - 0
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;
+}

+ 19 - 0
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;
+}

+ 21 - 0
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;
+}

+ 18 - 0
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;
+}

+ 19 - 0
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;
+}

+ 16 - 0
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();
+}

+ 12 - 0
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: <Main()>:0: attempt to call function `Main` that has not been defined
+
+package ExplorerTest api;
+
+fn Main() -> i32;

+ 1 - 3
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) {
 }
 

+ 19 - 0
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;
+}

+ 16 - 0
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;
+}

+ 19 - 0
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;
+}

+ 18 - 0
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;
+}

+ 23 - 0
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;
+}

+ 19 - 0
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;
+}

+ 19 - 0
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;
+}

+ 1 - 1
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 {