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>
This commit is contained in:
Richard Smith
2022-10-13 15:30:29 -07:00
committed by GitHub
co-authored by pmqtt Jon Ross-Perkins
parent e111418b32
commit a3329cf004
22 changed files with 481 additions and 68 deletions
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
@@ -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
View File
@@ -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
View File
@@ -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;
}
@@ -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
View File
@@ -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;
}
@@ -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
View File
@@ -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();
}
@@ -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
View File
@@ -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) {
}
@@ -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;
}
@@ -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;
}
@@ -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
View File
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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
View File
@@ -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 {