mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 07:24:42 +01:00
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:
co-authored by
pmqtt
Jon Ross-Perkins
parent
e111418b32
commit
a3329cf004
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user