diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 5839bdf8a2735..776f030764315 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -107,6 +107,7 @@ cc_library( ":import", ":interface", ":member_access", + ":pointer_dereference", "//common:check", "//common:ostream", "//toolchain/base:pretty_stack_trace_function", @@ -212,6 +213,21 @@ cc_library( ], ) +cc_library( + name = "pointer_dereference", + srcs = ["pointer_dereference.cpp"], + hdrs = ["pointer_dereference.h"], + deps = [ + ":context", + "//common:check", + "//toolchain/parse:node_kind", + "//toolchain/sem_ir:ids", + "//toolchain/sem_ir:inst", + "//toolchain/sem_ir:inst_kind", + "@llvm-project//llvm:Support", + ], +) + cc_library( name = "subst", srcs = ["subst.cpp"], diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 5ee800852d088..f9668272d1876 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -4,8 +4,8 @@ #include "toolchain/check/context.h" #include "toolchain/check/member_access.h" +#include "toolchain/check/pointer_dereference.h" #include "toolchain/lex/token_kind.h" -#include "toolchain/parse/typed_nodes.h" #include "toolchain/sem_ir/inst.h" #include "toolchain/sem_ir/typed_insts.h" @@ -31,7 +31,36 @@ auto HandleMemberAccessExpr(Context& context, Parse::MemberAccessExprId node_id) auto HandlePointerMemberAccessExpr(Context& context, Parse::PointerMemberAccessExprId node_id) -> bool { - return context.TODO(node_id, "HandlePointerMemberAccessExpr"); + auto diagnose_not_pointer = [&context, + &node_id](SemIR::TypeId not_pointer_type_id) { + CARBON_DIAGNOSTIC(ArrowOperatorOfNonPointer, Error, + "Cannot apply `->` operator to non-pointer type `{0}`.", + SemIR::TypeId); + + auto builder = context.emitter().Build( + TokenOnly(node_id), ArrowOperatorOfNonPointer, not_pointer_type_id); + builder.Emit(); + }; + + if (context.node_stack().PeekIs()) { + auto member_expr_id = context.node_stack().PopExpr(); + auto base_id = context.node_stack().PopExpr(); + auto deref_base_id = PerformPointerDereference(context, node_id, base_id, + diagnose_not_pointer); + auto member_id = PerformCompoundMemberAccess(context, node_id, + deref_base_id, member_expr_id); + context.node_stack().Push(node_id, member_id); + } else { + SemIR::NameId name_id = context.node_stack().PopName(); + auto base_id = context.node_stack().PopExpr(); + auto deref_base_id = PerformPointerDereference(context, node_id, base_id, + diagnose_not_pointer); + auto member_id = + PerformMemberAccess(context, node_id, deref_base_id, name_id); + context.node_stack().Push(node_id, member_id); + } + + return true; } static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id) diff --git a/toolchain/check/handle_operator.cpp b/toolchain/check/handle_operator.cpp index c0ed2126102a8..41435943243cf 100644 --- a/toolchain/check/handle_operator.cpp +++ b/toolchain/check/handle_operator.cpp @@ -4,6 +4,8 @@ #include "toolchain/check/context.h" #include "toolchain/check/convert.h" +#include "toolchain/check/pointer_dereference.h" +#include "toolchain/diagnostics/diagnostic_emitter.h" namespace Carbon::Check { @@ -281,30 +283,31 @@ auto HandlePrefixOperatorPlusPlus(Context& context, auto HandlePrefixOperatorStar(Context& context, Parse::PrefixOperatorStarId node_id) -> bool { - auto value_id = context.node_stack().PopExpr(); - value_id = ConvertToValueExpr(context, value_id); - auto type_id = - context.GetUnqualifiedType(context.insts().Get(value_id).type_id()); - auto result_type_id = SemIR::TypeId::Error; - if (auto pointer_type = - context.types().TryGetAs(type_id)) { - result_type_id = pointer_type->pointee_id; - } else if (type_id != SemIR::TypeId::Error) { - CARBON_DIAGNOSTIC(DerefOfNonPointer, Error, - "Cannot dereference operand of non-pointer type `{0}`.", - SemIR::TypeId); - auto builder = - context.emitter().Build(TokenOnly(node_id), DerefOfNonPointer, type_id); - // TODO: Check for any facet here, rather than only a type. - if (type_id == SemIR::TypeId::TypeType) { - CARBON_DIAGNOSTIC( - DerefOfType, Note, - "To form a pointer type, write the `*` after the pointee type."); - builder.Note(TokenOnly(node_id), DerefOfType); - } - builder.Emit(); - } - context.AddInstAndPush({node_id, SemIR::Deref{result_type_id, value_id}}); + auto base_id = context.node_stack().PopExpr(); + + auto deref_base_id = PerformPointerDereference( + context, node_id, base_id, + [&context, &node_id](SemIR::TypeId not_pointer_type_id) { + CARBON_DIAGNOSTIC( + DerefOfNonPointer, Error, + "Cannot dereference operand of non-pointer type `{0}`.", + SemIR::TypeId); + + auto builder = context.emitter().Build( + TokenOnly(node_id), DerefOfNonPointer, not_pointer_type_id); + + // TODO: Check for any facet here, rather than only a type. + if (not_pointer_type_id == SemIR::TypeId::TypeType) { + CARBON_DIAGNOSTIC( + DerefOfType, Note, + "To form a pointer type, write the `*` after the pointee type."); + builder.Note(TokenOnly(node_id), DerefOfType); + } + + builder.Emit(); + }); + + context.node_stack().Push(node_id, deref_base_id); return true; } diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index e8f0f9fe1c265..57e3c321fc0be 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -195,7 +195,7 @@ static auto PerformImplLookup(Context& context, SemIR::ConstantId type_const_id, // impl lookup if necessary. If the scope is invalid, assume an error has // already been diagnosed, and return BuiltinError. static auto LookupMemberNameInScope(Context& context, - Parse::MemberAccessExprId node_id, + Parse::AnyMemberAccessExprId node_id, SemIR::InstId /*base_id*/, SemIR::NameId name_id, SemIR::ConstantId name_scope_const_id, @@ -231,7 +231,7 @@ static auto LookupMemberNameInScope(Context& context, // field, forms a class member access. If the found member is an instance // method, forms a bound method. Otherwise, the member is returned unchanged. static auto PerformInstanceBinding(Context& context, - Parse::MemberAccessExprId node_id, + Parse::AnyMemberAccessExprId node_id, SemIR::InstId base_id, SemIR::InstId member_id) -> SemIR::InstId { auto member_type_id = context.insts().Get(member_id).type_id(); @@ -295,7 +295,7 @@ static auto PerformInstanceBinding(Context& context, return member_id; } -auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, +auto PerformMemberAccess(Context& context, Parse::AnyMemberAccessExprId node_id, SemIR::InstId base_id, SemIR::NameId name_id) -> SemIR::InstId { // If the base is a name scope, such as a class or namespace, perform lookup @@ -371,7 +371,7 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, } auto PerformCompoundMemberAccess(Context& context, - Parse::MemberAccessExprId node_id, + Parse::AnyMemberAccessExprId node_id, SemIR::InstId base_id, SemIR::InstId member_expr_id) -> SemIR::InstId { diff --git a/toolchain/check/member_access.h b/toolchain/check/member_access.h index bd3cf59b08fbb..f1f3594c92ab2 100644 --- a/toolchain/check/member_access.h +++ b/toolchain/check/member_access.h @@ -12,7 +12,7 @@ namespace Carbon::Check { // Creates SemIR to perform a member access with base expression `base_id` and // member name `name_id`. Returns the result of the access. -auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, +auto PerformMemberAccess(Context& context, Parse::AnyMemberAccessExprId node_id, SemIR::InstId base_id, SemIR::NameId name_id) -> SemIR::InstId; @@ -20,7 +20,7 @@ auto PerformMemberAccess(Context& context, Parse::MemberAccessExprId node_id, // `base_id` and member name expression `member_expr_id`. Returns the result of // the access. auto PerformCompoundMemberAccess(Context& context, - Parse::MemberAccessExprId node_id, + Parse::AnyMemberAccessExprId node_id, SemIR::InstId base_id, SemIR::InstId member_expr_id) -> SemIR::InstId; diff --git a/toolchain/check/pointer_dereference.cpp b/toolchain/check/pointer_dereference.cpp new file mode 100644 index 0000000000000..556ae5ee37410 --- /dev/null +++ b/toolchain/check/pointer_dereference.cpp @@ -0,0 +1,31 @@ +// 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 + +#include "llvm/ADT/STLFunctionalExtras.h" +#include "toolchain/check/context.h" +#include "toolchain/check/convert.h" +#include "toolchain/parse/node_ids.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::Check { + +auto PerformPointerDereference( + Context& context, Parse::AnyPointerDeferenceExprId node_id, + SemIR::InstId base_id, + llvm::function_refvoid> + diagnose_not_pointer) -> SemIR::InstId { + base_id = ConvertToValueExpr(context, base_id); + auto type_id = + context.GetUnqualifiedType(context.insts().Get(base_id).type_id()); + auto result_type_id = SemIR::TypeId::Error; + if (auto pointer_type = + context.types().TryGetAs(type_id)) { + result_type_id = pointer_type->pointee_id; + } else if (type_id != SemIR::TypeId::Error) { + diagnose_not_pointer(type_id); + } + return context.AddInst({node_id, SemIR::Deref{result_type_id, base_id}}); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/pointer_dereference.h b/toolchain/check/pointer_dereference.h new file mode 100644 index 0000000000000..aa281d99a1bf0 --- /dev/null +++ b/toolchain/check/pointer_dereference.h @@ -0,0 +1,25 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_POINTER_DEREFERENCE_H_ +#define CARBON_TOOLCHAIN_CHECK_POINTER_DEREFERENCE_H_ + +#include "llvm/ADT/STLFunctionalExtras.h" +#include "toolchain/check/context.h" +#include "toolchain/parse/node_ids.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::Check { + +// Creates SemIR to perform a pointer dereference with base expression +// `base_id`. Returns the result of the access. +auto PerformPointerDereference( + Context& context, Parse::AnyPointerDeferenceExprId node_id, + SemIR::InstId base_i, + llvm::function_refvoid> + diagnose_not_pointer) -> SemIR::InstId; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_POINTER_DEREFERENCE_H_ diff --git a/toolchain/check/testdata/class/fail_incomplete.carbon b/toolchain/check/testdata/class/fail_incomplete.carbon index 1a4e7fcb6527f..afd3dd23f9b68 100644 --- a/toolchain/check/testdata/class/fail_incomplete.carbon +++ b/toolchain/check/testdata/class/fail_incomplete.carbon @@ -40,16 +40,21 @@ var global_var: Class; // CHECK:STDERR: ^~~~~~~~~~~~ fn ConvertFromStruct() -> Class { return {}; } -// TODO: Once the `->` operator is supported: -// TODO: fn G(p: Class*) -> i32 { -// TODO: return p->n; -// TODO: } +fn G(p: Class*) -> i32 { + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: Member access into object of incomplete type `Class`. + // CHECK:STDERR: return p->n; + // CHECK:STDERR: ^~~~ + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-40]]:1: Class was forward declared here. + // CHECK:STDERR: class Class; + // CHECK:STDERR: ^~~~~~~~~~~~ + return p->n; +} fn MemberAccess(p: Class*) -> i32 { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:11: ERROR: Member access into object of incomplete type `Class`. // CHECK:STDERR: return (*p).n; // CHECK:STDERR: ^~ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-45]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-50]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ return (*p).n; @@ -58,7 +63,7 @@ fn MemberAccess(p: Class*) -> i32 { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:20: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn Copy(p: Class*) -> Class { // CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-54]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-59]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ fn Copy(p: Class*) -> Class { @@ -69,7 +74,7 @@ fn Let(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: `let` binding has incomplete type `Class`. // CHECK:STDERR: let c: Class = *p; // CHECK:STDERR: ^~~~~ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-65]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-70]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ let c: Class = *p; @@ -82,7 +87,7 @@ fn TakeIncomplete(c: Class); // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:23: ERROR: Function returns incomplete type `Class`. // CHECK:STDERR: fn ReturnIncomplete() -> Class; // CHECK:STDERR: ^~~~~~~~ -// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-78]]:1: Class was forward declared here. +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-83]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ fn ReturnIncomplete() -> Class; @@ -91,7 +96,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:3: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete(*p); // CHECK:STDERR: ^~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-87]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-92]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-19]]:1: Initializing parameter 1 of function declared here. @@ -102,7 +107,7 @@ fn CallTakeIncomplete(p: Class*) { // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:3: ERROR: Forming value of incomplete type `Class`. // CHECK:STDERR: TakeIncomplete({}); // CHECK:STDERR: ^~~~~~~~~~~~~~~ - // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-98]]:1: Class was forward declared here. + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-103]]:1: Class was forward declared here. // CHECK:STDERR: class Class; // CHECK:STDERR: ^~~~~~~~~~~~ // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-30]]:1: Initializing parameter 1 of function declared here. @@ -130,6 +135,7 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: .CallClassFunction = %CallClassFunction // CHECK:STDOUT: .global_var = %global_var // CHECK:STDOUT: .ConvertFromStruct = %ConvertFromStruct +// CHECK:STDOUT: .G = %G // CHECK:STDOUT: .MemberAccess = %MemberAccess // CHECK:STDOUT: .Copy = %Copy // CHECK:STDOUT: .Let = %Let @@ -148,41 +154,48 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: %Class.ref.loc41: type = name_ref Class, %Class.decl [template = constants.%Class] // CHECK:STDOUT: %return.var.loc41: ref Class = var // CHECK:STDOUT: } +// CHECK:STDOUT: %G: = fn_decl @G [template] { +// CHECK:STDOUT: %Class.ref.loc43: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %.loc43: type = ptr_type Class [template = constants.%.2] +// CHECK:STDOUT: %p.loc43_6.1: Class* = param p +// CHECK:STDOUT: @G.%p: Class* = bind_name p, %p.loc43_6.1 +// CHECK:STDOUT: %return.var.loc43: ref i32 = var +// CHECK:STDOUT: } // CHECK:STDOUT: %MemberAccess: = fn_decl @MemberAccess [template] { -// CHECK:STDOUT: %Class.ref.loc48: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %.loc48: type = ptr_type Class [template = constants.%.2] -// CHECK:STDOUT: %p.loc48_17.1: Class* = param p -// CHECK:STDOUT: @MemberAccess.%p: Class* = bind_name p, %p.loc48_17.1 -// CHECK:STDOUT: %return.var.loc48: ref i32 = var +// CHECK:STDOUT: %Class.ref.loc53: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %.loc53: type = ptr_type Class [template = constants.%.2] +// CHECK:STDOUT: %p.loc53_17.1: Class* = param p +// CHECK:STDOUT: @MemberAccess.%p: Class* = bind_name p, %p.loc53_17.1 +// CHECK:STDOUT: %return.var.loc53: ref i32 = var // CHECK:STDOUT: } // CHECK:STDOUT: %Copy: = fn_decl @Copy [template] { -// CHECK:STDOUT: %Class.ref.loc64_12: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %.loc64: type = ptr_type Class [template = constants.%.2] -// CHECK:STDOUT: %p.loc64_9.1: Class* = param p -// CHECK:STDOUT: @Copy.%p: Class* = bind_name p, %p.loc64_9.1 -// CHECK:STDOUT: %Class.ref.loc64_23: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %return.var.loc64: ref Class = var +// CHECK:STDOUT: %Class.ref.loc69_12: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %.loc69: type = ptr_type Class [template = constants.%.2] +// CHECK:STDOUT: %p.loc69_9.1: Class* = param p +// CHECK:STDOUT: @Copy.%p: Class* = bind_name p, %p.loc69_9.1 +// CHECK:STDOUT: %Class.ref.loc69_23: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %return.var.loc69: ref Class = var // CHECK:STDOUT: } // CHECK:STDOUT: %Let: = fn_decl @Let [template] { -// CHECK:STDOUT: %Class.ref.loc68: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %.loc68: type = ptr_type Class [template = constants.%.2] -// CHECK:STDOUT: %p.loc68_8.1: Class* = param p -// CHECK:STDOUT: @Let.%p: Class* = bind_name p, %p.loc68_8.1 +// CHECK:STDOUT: %Class.ref.loc73: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %.loc73: type = ptr_type Class [template = constants.%.2] +// CHECK:STDOUT: %p.loc73_8.1: Class* = param p +// CHECK:STDOUT: @Let.%p: Class* = bind_name p, %p.loc73_8.1 // CHECK:STDOUT: } // CHECK:STDOUT: %TakeIncomplete: = fn_decl @TakeIncomplete [template] { -// CHECK:STDOUT: %Class.ref.loc78: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %c.loc78_19.1: Class = param c -// CHECK:STDOUT: @TakeIncomplete.%c: Class = bind_name c, %c.loc78_19.1 +// CHECK:STDOUT: %Class.ref.loc83: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %c.loc83_19.1: Class = param c +// CHECK:STDOUT: @TakeIncomplete.%c: Class = bind_name c, %c.loc83_19.1 // CHECK:STDOUT: } // CHECK:STDOUT: %ReturnIncomplete: = fn_decl @ReturnIncomplete [template] { -// CHECK:STDOUT: %Class.ref.loc88: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %return.var.loc88: ref Class = var +// CHECK:STDOUT: %Class.ref.loc93: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %return.var.loc93: ref Class = var // CHECK:STDOUT: } // CHECK:STDOUT: %CallTakeIncomplete: = fn_decl @CallTakeIncomplete [template] { -// CHECK:STDOUT: %Class.ref.loc90: type = name_ref Class, %Class.decl [template = constants.%Class] -// CHECK:STDOUT: %.loc90: type = ptr_type Class [template = constants.%.2] -// CHECK:STDOUT: %p.loc90_23.1: Class* = param p -// CHECK:STDOUT: @CallTakeIncomplete.%p: Class* = bind_name p, %p.loc90_23.1 +// CHECK:STDOUT: %Class.ref.loc95: type = name_ref Class, %Class.decl [template = constants.%Class] +// CHECK:STDOUT: %.loc95: type = ptr_type Class [template = constants.%.2] +// CHECK:STDOUT: %p.loc95_23.1: Class* = param p +// CHECK:STDOUT: @CallTakeIncomplete.%p: Class* = bind_name p, %p.loc95_23.1 // CHECK:STDOUT: } // CHECK:STDOUT: %CallReturnIncomplete: = fn_decl @CallReturnIncomplete [template] {} // CHECK:STDOUT: } @@ -207,17 +220,24 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @G(%p: Class*) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: Class* = name_ref p, %p +// CHECK:STDOUT: %.loc50: ref Class = deref %p.ref +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: fn @MemberAccess(%p: Class*) -> i32 { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_ref p, %p -// CHECK:STDOUT: %.loc55: ref Class = deref %p.ref +// CHECK:STDOUT: %.loc60: ref Class = deref %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Copy(%p: Class*) -> { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %p.ref: Class* = name_ref p, %p -// CHECK:STDOUT: %.loc65: ref Class = deref %p.ref +// CHECK:STDOUT: %.loc70: ref Class = deref %p.ref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: @@ -225,7 +245,7 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Class.ref: type = name_ref Class, file.%Class.decl [template = constants.%Class] // CHECK:STDOUT: %p.ref: Class* = name_ref p, %p -// CHECK:STDOUT: %.loc75: ref Class = deref %p.ref +// CHECK:STDOUT: %.loc80: ref Class = deref %p.ref // CHECK:STDOUT: %c: = bind_name c, // CHECK:STDOUT: return // CHECK:STDOUT: } @@ -236,20 +256,20 @@ fn CallReturnIncomplete() { // CHECK:STDOUT: // CHECK:STDOUT: fn @CallTakeIncomplete(%p: Class*) { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %TakeIncomplete.ref.loc100: = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete] +// CHECK:STDOUT: %TakeIncomplete.ref.loc105: = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete] // CHECK:STDOUT: %p.ref: Class* = name_ref p, %p -// CHECK:STDOUT: %.loc100_18: ref Class = deref %p.ref -// CHECK:STDOUT: %.loc100_17: init () = call %TakeIncomplete.ref.loc100() -// CHECK:STDOUT: %TakeIncomplete.ref.loc111: = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete] -// CHECK:STDOUT: %.loc111_19: {} = struct_literal () -// CHECK:STDOUT: %.loc111_17: init () = call %TakeIncomplete.ref.loc111() +// CHECK:STDOUT: %.loc105_18: ref Class = deref %p.ref +// CHECK:STDOUT: %.loc105_17: init () = call %TakeIncomplete.ref.loc105() +// CHECK:STDOUT: %TakeIncomplete.ref.loc116: = name_ref TakeIncomplete, file.%TakeIncomplete [template = file.%TakeIncomplete] +// CHECK:STDOUT: %.loc116_19: {} = struct_literal () +// CHECK:STDOUT: %.loc116_17: init () = call %TakeIncomplete.ref.loc116() // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @CallReturnIncomplete() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %ReturnIncomplete.ref: = name_ref ReturnIncomplete, file.%ReturnIncomplete [template = file.%ReturnIncomplete] -// CHECK:STDOUT: %.loc115: init = call %ReturnIncomplete.ref() +// CHECK:STDOUT: %.loc120: init = call %ReturnIncomplete.ref() // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/arrow.carbon b/toolchain/check/testdata/pointer/arrow.carbon new file mode 100644 index 0000000000000..1b55a2c862176 --- /dev/null +++ b/toolchain/check/testdata/pointer/arrow.carbon @@ -0,0 +1,97 @@ +// 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 +// +// AUTOUPDATE + +class C { + fn Member[self: Self](); + var field: C*; +} + +fn Foo(ptr: C*) { + (*ptr).Member(); + ptr->Member(); + + (*ptr).field; + ptr->field; + + ptr->field->field; +} + +// CHECK:STDOUT: --- arrow.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %.1: type = ptr_type C [template] +// CHECK:STDOUT: %.2: type = unbound_element_type C, C* [template] +// CHECK:STDOUT: %.3: type = struct_type {.field: C*} [template] +// CHECK:STDOUT: %.4: type = ptr_type {.field: C*} [template] +// CHECK:STDOUT: %.5: type = tuple_type () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .C = %C.decl +// CHECK:STDOUT: .Foo = %Foo +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} +// CHECK:STDOUT: %Foo: = fn_decl @Foo [template] { +// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [template = constants.%C] +// CHECK:STDOUT: %.loc12: type = ptr_type C [template = constants.%.1] +// CHECK:STDOUT: %ptr.loc12_8.1: C* = param ptr +// CHECK:STDOUT: @Foo.%ptr: C* = bind_name ptr, %ptr.loc12_8.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %Member: = fn_decl @Member [template] { +// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%C [template = constants.%C] +// CHECK:STDOUT: %self.loc8_13.1: C = param self +// CHECK:STDOUT: %self.loc8_13.2: C = bind_name self, %self.loc8_13.1 +// CHECK:STDOUT: } +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [template = constants.%C] +// CHECK:STDOUT: %.loc9_15: type = ptr_type C [template = constants.%.1] +// CHECK:STDOUT: %.loc9_12: = field_decl field, element0 [template] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .Member = %Member +// CHECK:STDOUT: .field = %.loc9_12 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Member[@C.%self.loc8_13.2: C](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Foo(%ptr: C*) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %ptr.ref.loc13: C* = name_ref ptr, %ptr +// CHECK:STDOUT: %.loc13_4.1: ref C = deref %ptr.ref.loc13 +// CHECK:STDOUT: %Member.ref.loc13: = name_ref Member, @C.%Member [template = @C.%Member] +// CHECK:STDOUT: %.loc13_9: = bound_method %.loc13_4.1, %Member.ref.loc13 +// CHECK:STDOUT: %.loc13_4.2: C = bind_value %.loc13_4.1 +// CHECK:STDOUT: %.loc13_16: init () = call %.loc13_9(%.loc13_4.2) +// CHECK:STDOUT: %ptr.ref.loc14: C* = name_ref ptr, %ptr +// CHECK:STDOUT: %.loc14_6.1: ref C = deref %ptr.ref.loc14 +// CHECK:STDOUT: %Member.ref.loc14: = name_ref Member, @C.%Member [template = @C.%Member] +// CHECK:STDOUT: %.loc14_6.2: = bound_method %.loc14_6.1, %Member.ref.loc14 +// CHECK:STDOUT: %.loc14_6.3: C = bind_value %.loc14_6.1 +// CHECK:STDOUT: %.loc14_14: init () = call %.loc14_6.2(%.loc14_6.3) +// CHECK:STDOUT: %ptr.ref.loc16: C* = name_ref ptr, %ptr +// CHECK:STDOUT: %.loc16_4: ref C = deref %ptr.ref.loc16 +// CHECK:STDOUT: %field.ref.loc16: = name_ref field, @C.%.loc9_12 [template = @C.%.loc9_12] +// CHECK:STDOUT: %.loc16_9: ref C* = class_element_access %.loc16_4, element0 +// CHECK:STDOUT: %ptr.ref.loc17: C* = name_ref ptr, %ptr +// CHECK:STDOUT: %.loc17_6.1: ref C = deref %ptr.ref.loc17 +// CHECK:STDOUT: %field.ref.loc17: = name_ref field, @C.%.loc9_12 [template = @C.%.loc9_12] +// CHECK:STDOUT: %.loc17_6.2: ref C* = class_element_access %.loc17_6.1, element0 +// CHECK:STDOUT: %ptr.ref.loc19: C* = name_ref ptr, %ptr +// CHECK:STDOUT: %.loc19_6.1: ref C = deref %ptr.ref.loc19 +// CHECK:STDOUT: %field.ref.loc19_6: = name_ref field, @C.%.loc9_12 [template = @C.%.loc9_12] +// CHECK:STDOUT: %.loc19_6.2: ref C* = class_element_access %.loc19_6.1, element0 +// CHECK:STDOUT: %.loc19_6.3: C* = bind_value %.loc19_6.2 +// CHECK:STDOUT: %.loc19_13.1: ref C = deref %.loc19_6.3 +// CHECK:STDOUT: %field.ref.loc19_13: = name_ref field, @C.%.loc9_12 [template = @C.%.loc9_12] +// CHECK:STDOUT: %.loc19_13.2: ref C* = class_element_access %.loc19_13.1, element0 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/fail_deref_error.carbon b/toolchain/check/testdata/pointer/fail_deref_error.carbon index 1c11e1c73f98e..81591eab7b290 100644 --- a/toolchain/check/testdata/pointer/fail_deref_error.carbon +++ b/toolchain/check/testdata/pointer/fail_deref_error.carbon @@ -8,13 +8,20 @@ // CHECK:STDERR: let n: i32 = *undeclared; // CHECK:STDERR: ^~~~~~~~~~ let n: i32 = *undeclared; +// CHECK:STDERR: fail_deref_error.carbon:[[@LINE+3]]:15: ERROR: Name `undeclared` not found. +// CHECK:STDERR: let n2: i32 = undeclared->foo; +// CHECK:STDERR: ^~~~~~~~~~ +let n2: i32 = undeclared->foo; // CHECK:STDOUT: --- fail_deref_error.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: package: = namespace [template] {} -// CHECK:STDOUT: %undeclared.ref: = name_ref undeclared, [template = ] +// CHECK:STDOUT: %undeclared.ref.loc10: = name_ref undeclared, [template = ] // CHECK:STDOUT: %.loc10: ref = deref // CHECK:STDOUT: %n: i32 = bind_name n, +// CHECK:STDOUT: %undeclared.ref.loc14: = name_ref undeclared, [template = ] +// CHECK:STDOUT: %.loc14: ref = deref +// CHECK:STDOUT: %n2: i32 = bind_name n2, // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/fail_deref_function.carbon b/toolchain/check/testdata/pointer/fail_deref_function.carbon index 67dedc284bdd1..0251a7f83d295 100644 --- a/toolchain/check/testdata/pointer/fail_deref_function.carbon +++ b/toolchain/check/testdata/pointer/fail_deref_function.carbon @@ -9,6 +9,10 @@ fn A() { // CHECK:STDERR: *A; // CHECK:STDERR: ^ *A; + // CHECK:STDERR: fail_deref_function.carbon:[[@LINE+3]]:3: ERROR: Expression cannot be used as a value. + // CHECK:STDERR: A->foo; + // CHECK:STDERR: ^ + A->foo; } // CHECK:STDOUT: --- fail_deref_function.carbon @@ -22,8 +26,10 @@ fn A() { // CHECK:STDOUT: // CHECK:STDOUT: fn @A() { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %A.ref: = name_ref A, file.%A [template = file.%A] +// CHECK:STDOUT: %A.ref.loc11: = name_ref A, file.%A [template = file.%A] // CHECK:STDOUT: %.loc11: ref = deref +// CHECK:STDOUT: %A.ref.loc15: = name_ref A, file.%A [template = file.%A] +// CHECK:STDOUT: %.loc15: ref = deref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/fail_deref_namespace.carbon b/toolchain/check/testdata/pointer/fail_deref_namespace.carbon index 00a413480a032..643d4b1028744 100644 --- a/toolchain/check/testdata/pointer/fail_deref_namespace.carbon +++ b/toolchain/check/testdata/pointer/fail_deref_namespace.carbon @@ -11,6 +11,10 @@ fn F() { // CHECK:STDERR: *A; // CHECK:STDERR: ^ *A; + // CHECK:STDERR: fail_deref_namespace.carbon:[[@LINE+3]]:3: ERROR: Expression cannot be used as a value. + // CHECK:STDERR: A->foo; + // CHECK:STDERR: ^ + A->foo; } // CHECK:STDOUT: --- fail_deref_namespace.carbon @@ -26,8 +30,10 @@ fn F() { // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %A.ref: = name_ref A, file.%A [template = file.%A] +// CHECK:STDOUT: %A.ref.loc13: = name_ref A, file.%A [template = file.%A] // CHECK:STDOUT: %.loc13: ref = deref +// CHECK:STDOUT: %A.ref.loc17: = name_ref A, file.%A [template = file.%A] +// CHECK:STDOUT: %.loc17: ref = deref // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/fail_deref_not_pointer.carbon b/toolchain/check/testdata/pointer/fail_deref_not_pointer.carbon index c5a008656da3d..e995b2b945184 100644 --- a/toolchain/check/testdata/pointer/fail_deref_not_pointer.carbon +++ b/toolchain/check/testdata/pointer/fail_deref_not_pointer.carbon @@ -9,14 +9,26 @@ fn Deref(n: i32) { // CHECK:STDERR: *n; // CHECK:STDERR: ^ *n; + // CHECK:STDERR: fail_deref_not_pointer.carbon:[[@LINE+3]]:4: ERROR: Cannot apply `->` operator to non-pointer type `i32`. + // CHECK:STDERR: n->foo; + // CHECK:STDERR: ^~ + n->foo; // CHECK:STDERR: fail_deref_not_pointer.carbon:[[@LINE+3]]:3: ERROR: Cannot dereference operand of non-pointer type `()`. // CHECK:STDERR: *(); // CHECK:STDERR: ^ *(); + // CHECK:STDERR: fail_deref_not_pointer.carbon:[[@LINE+3]]:5: ERROR: Cannot apply `->` operator to non-pointer type `()`. + // CHECK:STDERR: ()->foo; + // CHECK:STDERR: ^~ + ()->foo; // CHECK:STDERR: fail_deref_not_pointer.carbon:[[@LINE+3]]:3: ERROR: Cannot dereference operand of non-pointer type `{}`. // CHECK:STDERR: *{}; // CHECK:STDERR: ^ *{}; + // CHECK:STDERR: fail_deref_not_pointer.carbon:[[@LINE+3]]:5: ERROR: Cannot apply `->` operator to non-pointer type `{}`. + // CHECK:STDERR: {}->foo; + // CHECK:STDERR: ^~ + {}->foo; } // CHECK:STDOUT: --- fail_deref_not_pointer.carbon @@ -40,16 +52,26 @@ fn Deref(n: i32) { // CHECK:STDOUT: // CHECK:STDOUT: fn @Deref(%n: i32) { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %n.ref: i32 = name_ref n, %n -// CHECK:STDOUT: %.loc11: ref = deref %n.ref -// CHECK:STDOUT: %.loc15_5.1: () = tuple_literal () -// CHECK:STDOUT: %.loc15_5.2: () = tuple_value () [template = constants.%.2] -// CHECK:STDOUT: %.loc15_5.3: () = converted %.loc15_5.1, %.loc15_5.2 [template = constants.%.2] -// CHECK:STDOUT: %.loc15_3: ref = deref %.loc15_5.3 -// CHECK:STDOUT: %.loc19_5.1: {} = struct_literal () -// CHECK:STDOUT: %.loc19_5.2: {} = struct_value () [template = constants.%.4] -// CHECK:STDOUT: %.loc19_5.3: {} = converted %.loc19_5.1, %.loc19_5.2 [template = constants.%.4] +// CHECK:STDOUT: %n.ref.loc11: i32 = name_ref n, %n +// CHECK:STDOUT: %.loc11: ref = deref %n.ref.loc11 +// CHECK:STDOUT: %n.ref.loc15: i32 = name_ref n, %n +// CHECK:STDOUT: %.loc15: ref = deref %n.ref.loc15 +// CHECK:STDOUT: %.loc19_5.1: () = tuple_literal () +// CHECK:STDOUT: %.loc19_5.2: () = tuple_value () [template = constants.%.2] +// CHECK:STDOUT: %.loc19_5.3: () = converted %.loc19_5.1, %.loc19_5.2 [template = constants.%.2] // CHECK:STDOUT: %.loc19_3: ref = deref %.loc19_5.3 +// CHECK:STDOUT: %.loc23_4.1: () = tuple_literal () +// CHECK:STDOUT: %.loc23_4.2: () = tuple_value () [template = constants.%.2] +// CHECK:STDOUT: %.loc23_4.3: () = converted %.loc23_4.1, %.loc23_4.2 [template = constants.%.2] +// CHECK:STDOUT: %.loc23_5: ref = deref %.loc23_4.3 +// CHECK:STDOUT: %.loc27_5.1: {} = struct_literal () +// CHECK:STDOUT: %.loc27_5.2: {} = struct_value () [template = constants.%.4] +// CHECK:STDOUT: %.loc27_5.3: {} = converted %.loc27_5.1, %.loc27_5.2 [template = constants.%.4] +// CHECK:STDOUT: %.loc27_3: ref = deref %.loc27_5.3 +// CHECK:STDOUT: %.loc31_4.1: {} = struct_literal () +// CHECK:STDOUT: %.loc31_4.2: {} = struct_value () [template = constants.%.4] +// CHECK:STDOUT: %.loc31_4.3: {} = converted %.loc31_4.1, %.loc31_4.2 [template = constants.%.4] +// CHECK:STDOUT: %.loc31_5: ref = deref %.loc31_4.3 // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/pointer/fail_deref_type.carbon b/toolchain/check/testdata/pointer/fail_deref_type.carbon index 7695b85f0c9f7..d37b984f06f4c 100644 --- a/toolchain/check/testdata/pointer/fail_deref_type.carbon +++ b/toolchain/check/testdata/pointer/fail_deref_type.carbon @@ -11,15 +11,23 @@ // CHECK:STDERR: var p: *i32; // CHECK:STDERR: ^ var p: *i32; +// CHECK:STDERR: fail_deref_type.carbon:[[@LINE+3]]:12: ERROR: Cannot apply `->` operator to non-pointer type `type`. +// CHECK:STDERR: var p2: i32->foo; +// CHECK:STDERR: ^~ +var p2: i32->foo; // CHECK:STDOUT: --- fail_deref_type.carbon // CHECK:STDOUT: // CHECK:STDOUT: file { // CHECK:STDOUT: package: = namespace [template] { // CHECK:STDOUT: .p = %p +// CHECK:STDOUT: .p2 = %p2 // CHECK:STDOUT: } // CHECK:STDOUT: %.loc13: ref = deref i32 // CHECK:STDOUT: %p.var: ref = var p // CHECK:STDOUT: %p: ref = bind_name p, %p.var +// CHECK:STDOUT: %.loc17: ref = deref i32 +// CHECK:STDOUT: %p2.var: ref = var p2 +// CHECK:STDOUT: %p2: ref = bind_name p2, %p2.var // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 0a21be17f928e..8ee4cfa125bfc 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -227,6 +227,7 @@ CARBON_DIAGNOSTIC_KIND(ArrayBoundTooLarge) CARBON_DIAGNOSTIC_KIND(ArrayIndexOutOfBounds) CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch) CARBON_DIAGNOSTIC_KIND(ArrayInitFromExprArgCountMismatch) +CARBON_DIAGNOSTIC_KIND(ArrowOperatorOfNonPointer) CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable) CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop) CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop) diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h index 1196e22e21342..6fa15b152bd9c 100644 --- a/toolchain/parse/node_ids.h +++ b/toolchain/parse/node_ids.h @@ -94,6 +94,10 @@ using AnyImplDeclId = NodeIdOneOf; using AnyInterfaceDeclId = NodeIdOneOf; using AnyNamespaceId = NodeIdOneOf; +using AnyMemberAccessExprId = + NodeIdOneOf; +using AnyPointerDeferenceExprId = + NodeIdOneOf; // NodeId with kind that is anything but T::Kind. template diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 852fbdc3a724d..728d0b2ce3f10 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -273,7 +273,8 @@ struct BoolLiteral { // `self` parameter, such as `object.MethodName`. struct BoundMethod { static constexpr auto Kind = - InstKind::BoundMethod.Define("bound_method"); + InstKind::BoundMethod.Define( + "bound_method"); TypeId type_id; // The object argument in the bound method, which will be used to initialize