Просмотр исходного кода

Implement 'alias' with transparent semantics. (#3701)

Adds `BindAlias` with a hybrid of `BindName` and `NameRef` semantics. I
think it's slightly closer to `BindName` because it introduces a name,
so I'm going more in that direction. This also matches the need for
`bind_name_id` with imports on enclosing scopes.

Note, only things that look like a name reference are being allowed on
the RHS of `alias`. This includes builtins that look like name
references, such as `bool`, but not ones that turn into values
underneath, such as `false`.
Jon Ross-Perkins 2 лет назад
Родитель
Сommit
1437b0e26d
40 измененных файлов с 1312 добавлено и 92 удалено
  1. 1 0
      toolchain/check/context.cpp
  2. 1 0
      toolchain/check/decl_state.h
  3. 4 1
      toolchain/check/eval.cpp
  4. 75 0
      toolchain/check/handle_alias.cpp
  5. 1 0
      toolchain/check/import.cpp
  6. 19 3
      toolchain/check/import_ref.cpp
  7. 29 0
      toolchain/check/testdata/alias/alias_of_alias.carbon
  8. 31 0
      toolchain/check/testdata/alias/builtins.carbon
  9. 31 0
      toolchain/check/testdata/alias/fail_aliased_name_in_diag.carbon
  10. 26 0
      toolchain/check/testdata/alias/fail_bool_value.carbon
  11. 29 0
      toolchain/check/testdata/alias/fail_control_flow.carbon
  12. 43 0
      toolchain/check/testdata/alias/fail_local_in_namespace.carbon
  13. 42 0
      toolchain/check/testdata/alias/fail_modifiers.carbon
  14. 50 0
      toolchain/check/testdata/alias/fail_name_conflict.carbon
  15. 46 0
      toolchain/check/testdata/alias/fail_not_constant.carbon
  16. 18 0
      toolchain/check/testdata/alias/fail_todo_private.carbon
  17. 61 0
      toolchain/check/testdata/alias/import.carbon
  18. 88 0
      toolchain/check/testdata/alias/import_order.carbon
  19. 38 0
      toolchain/check/testdata/alias/in_namespace.carbon
  20. 36 0
      toolchain/check/testdata/alias/local.carbon
  21. 34 19
      toolchain/check/testdata/class/fail_method.carbon
  22. 83 63
      toolchain/check/testdata/class/method.carbon
  23. 44 0
      toolchain/check/testdata/function/call/alias.carbon
  24. 62 0
      toolchain/check/testdata/namespace/alias.carbon
  25. 39 0
      toolchain/check/testdata/namespace/fail_decl_in_alias.carbon
  26. 6 0
      toolchain/diagnostics/diagnostic_kind.def
  27. 10 0
      toolchain/lower/handle.cpp
  28. 21 0
      toolchain/lower/testdata/alias/local.carbon
  29. 61 0
      toolchain/parse/handle_alias.cpp
  30. 5 0
      toolchain/parse/handle_decl_scope_loop.cpp
  31. 5 0
      toolchain/parse/handle_statement.cpp
  32. 11 0
      toolchain/parse/node_kind.def
  33. 34 0
      toolchain/parse/state.def
  34. 69 0
      toolchain/parse/testdata/alias/basic.carbon
  35. 117 0
      toolchain/parse/testdata/alias/fail_syntax.carbon
  36. 19 0
      toolchain/parse/typed_nodes.h
  37. 8 2
      toolchain/sem_ir/file.cpp
  38. 1 0
      toolchain/sem_ir/formatter.cpp
  39. 2 1
      toolchain/sem_ir/inst_kind.def
  40. 12 3
      toolchain/sem_ir/typed_insts.h

+ 1 - 0
toolchain/check/context.cpp

@@ -849,6 +849,7 @@ class TypeCompleter {
       case SemIR::ArrayInit::Kind:
       case SemIR::Assign::Kind:
       case SemIR::BaseDecl::Kind:
+      case SemIR::BindAlias::Kind:
       case SemIR::BindName::Kind:
       case SemIR::BindValue::Kind:
       case SemIR::BlockArg::Kind:

+ 1 - 0
toolchain/check/decl_state.h

@@ -53,6 +53,7 @@ struct DeclState {
   // The kind of declaration.
   enum DeclKind : int8_t {
     FileScope,
+    Alias,
     Base,
     Class,
     Constraint,

+ 4 - 1
toolchain/check/eval.cpp

@@ -416,7 +416,10 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       // are treated as the same value.
       return SemIR::ConstantId::ForSymbolicConstant(inst_id);
 
-    // These semnatic wrappers don't change the constant value.
+    // These semantic wrappers don't change the constant value.
+    case SemIR::BindAlias::Kind:
+      return context.constant_values().Get(
+          inst.As<SemIR::BindAlias>().value_id);
     case SemIR::NameRef::Kind:
       return context.constant_values().Get(inst.As<SemIR::NameRef>().value_id);
     case SemIR::Converted::Kind:

+ 75 - 0
toolchain/check/handle_alias.cpp

@@ -0,0 +1,75 @@
+// 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 "toolchain/check/context.h"
+#include "toolchain/check/modifiers.h"
+#include "toolchain/parse/node_ids.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/typed_insts.h"
+#include "toolchain/sem_ir/value_stores.h"
+
+namespace Carbon::Check {
+
+auto HandleAliasIntroducer(Context& context,
+                           Parse::AliasIntroducerId /*parse_node*/) -> bool {
+  context.decl_state_stack().Push(DeclState::Alias);
+  context.decl_name_stack().PushScopeAndStartName();
+  return true;
+}
+
+auto HandleAliasInitializer(Context& /*context*/,
+                            Parse::AliasInitializerId /*parse_node*/) -> bool {
+  return true;
+}
+
+auto HandleAlias(Context& context, Parse::AliasId /*parse_node*/) -> bool {
+  auto [expr_node, expr_id] = context.node_stack().PopExprWithParseNode();
+
+  auto name_context = context.decl_name_stack().FinishName();
+
+  LimitModifiersOnDecl(context, KeywordModifierSet::Access,
+                       Lex::TokenKind::Alias);
+  auto modifiers = context.decl_state_stack().innermost().modifier_set;
+  if (!!(modifiers & KeywordModifierSet::Access)) {
+    context.TODO(context.decl_state_stack().innermost().saw_access_modifier,
+                 "access modifier");
+  }
+  context.decl_state_stack().Pop(DeclState::Alias);
+
+  auto bind_name_id = context.bind_names().Add(
+      {.name_id = name_context.name_id_for_new_inst(),
+       .enclosing_scope_id = name_context.enclosing_scope_id_for_new_inst()});
+
+  auto alias_id = SemIR::InstId::Invalid;
+  if (expr_id.is_builtin()) {
+    // Type (`bool`) and value (`false`) literals provided by the builtin
+    // structure should be turned into name references.
+    // TODO: Look into handling `false`, this doesn't do it right now because it
+    // sees a value instruction instead of a builtin.
+    alias_id = context.AddInst(
+        {name_context.parse_node,
+         SemIR::BindAlias{context.insts().Get(expr_id).type_id(), bind_name_id,
+                          expr_id}});
+  } else if (auto inst = context.insts().TryGetAs<SemIR::NameRef>(expr_id)) {
+    // Pass through name references, albeit changing the name in use.
+    alias_id = context.AddInst(
+        {name_context.parse_node,
+         SemIR::BindAlias{inst->type_id, bind_name_id, inst->value_id}});
+  } else {
+    CARBON_DIAGNOSTIC(AliasRequiresNameRef, Error,
+                      "Alias initializer must be a name reference.");
+    context.emitter().Emit(expr_node, AliasRequiresNameRef);
+    alias_id =
+        context.AddInst({name_context.parse_node,
+                         SemIR::BindAlias{SemIR::TypeId::Error, bind_name_id,
+                                          SemIR::InstId::BuiltinError}});
+  }
+
+  // Add the name of the binding to the current scope.
+  context.decl_name_stack().PopScope();
+  context.decl_name_stack().AddNameToLookup(name_context, alias_id);
+  return true;
+}
+
+}  // namespace Carbon::Check

+ 1 - 0
toolchain/check/import.cpp

@@ -21,6 +21,7 @@ static auto GetImportName(const SemIR::File& import_sem_ir,
                           SemIR::Inst import_inst)
     -> std::pair<SemIR::NameId, SemIR::NameScopeId> {
   switch (import_inst.kind()) {
+    case SemIR::InstKind::BindAlias:
     case SemIR::InstKind::BindName:
     case SemIR::InstKind::BindSymbolicName: {
       const auto& bind_name = import_sem_ir.bind_names().Get(

+ 19 - 3
toolchain/check/import_ref.cpp

@@ -73,10 +73,9 @@ class ImportRefResolver {
   }
 
  private:
-  // Returns the ConstantId for an instruction, or adds it to the stack and
+  // Returns the ConstantId for an InstId, or adds it to the stack and
   // returns Invalid if the ConstantId is not ready.
-  auto GetConstantId(SemIR::TypeId type_id) -> SemIR::ConstantId {
-    auto inst_id = import_ir_.types().GetInstId(type_id);
+  auto GetConstantId(SemIR::InstId inst_id) -> SemIR::ConstantId {
     auto const_id = import_ir_constant_values_.Get(inst_id);
     if (!const_id.is_valid()) {
       work_stack_.push_back(inst_id);
@@ -84,6 +83,12 @@ class ImportRefResolver {
     return const_id;
   }
 
+  // Returns the ConstantId for a TypeId, or adds it to the stack and
+  // returns Invalid if the ConstantId is not ready.
+  auto GetConstantId(SemIR::TypeId type_id) -> SemIR::ConstantId {
+    return GetConstantId(import_ir_.types().GetInstId(type_id));
+  }
+
   // Tries to resolve the InstId, returning a constant when ready, or Invalid if
   // more has been added to the stack. A similar API is followed for all
   // following TryResolveTypedInst helper functions.
@@ -109,6 +114,9 @@ class ImportRefResolver {
 
     auto inst = import_ir_.insts().Get(inst_id);
     switch (inst.kind()) {
+      case SemIR::InstKind::BindAlias:
+        return TryResolveTypedInst(inst.As<SemIR::BindAlias>());
+
       case SemIR::InstKind::ConstType:
         return TryResolveTypedInst(inst.As<SemIR::ConstType>());
 
@@ -144,6 +152,14 @@ class ImportRefResolver {
     }
   }
 
+  auto TryResolveTypedInst(SemIR::BindAlias inst) -> SemIR::ConstantId {
+    auto value_id = GetConstantId(inst.value_id);
+    if (!value_id.is_valid()) {
+      return SemIR::ConstantId::Invalid;
+    }
+    return value_id;
+  }
+
   auto TryResolveTypedInst(SemIR::ConstType inst) -> SemIR::ConstantId {
     CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
     auto inner_const_id = GetConstantId(inst.inner_id);

+ 29 - 0
toolchain/check/testdata/alias/alias_of_alias.carbon

@@ -0,0 +1,29 @@
+// 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
+
+alias a = bool;
+alias b = a;
+alias c = b;
+let d: c = false;
+
+// CHECK:STDOUT: --- alias_of_alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, .b = %b, .c = %c} [template]
+// CHECK:STDOUT:   %a: type = bind_alias a, bool [template = bool]
+// CHECK:STDOUT:   %a.ref: type = name_ref a, %a [template = bool]
+// CHECK:STDOUT:   %b: type = bind_alias b, %a [template = bool]
+// CHECK:STDOUT:   %b.ref: type = name_ref b, %b [template = bool]
+// CHECK:STDOUT:   %c: type = bind_alias c, %b [template = bool]
+// CHECK:STDOUT:   %c.ref: type = name_ref c, %c [template = bool]
+// CHECK:STDOUT:   %.loc10: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   %d: bool = bind_name d, %.loc10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 31 - 0
toolchain/check/testdata/alias/builtins.carbon

@@ -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
+//
+// AUTOUPDATE
+
+alias a = i32;
+let a_test: a = 0;
+
+alias b = bool;
+let b_test: b = false;
+
+// CHECK:STDOUT: --- builtins.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, .b = %b} [template]
+// CHECK:STDOUT:   %a: type = bind_alias a, i32 [template = i32]
+// CHECK:STDOUT:   %a.ref: type = name_ref a, %a [template = i32]
+// CHECK:STDOUT:   %.loc8: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   %a_test: i32 = bind_name a_test, %.loc8
+// CHECK:STDOUT:   %b: type = bind_alias b, bool [template = bool]
+// CHECK:STDOUT:   %b.ref: type = name_ref b, %b [template = bool]
+// CHECK:STDOUT:   %.loc11: bool = bool_literal false [template = constants.%.2]
+// CHECK:STDOUT:   %b_test: bool = bind_name b_test, %.loc11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 31 - 0
toolchain/check/testdata/alias/fail_aliased_name_in_diag.carbon

@@ -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
+//
+// AUTOUPDATE
+
+let a: i32 = 1;
+alias b = a;
+// CHECK:STDERR: fail_aliased_name_in_diag.carbon:[[@LINE+3]]:8: ERROR: Cannot implicitly convert from `i32` to `type`.
+// CHECK:STDERR: let c: b = 2;
+// CHECK:STDERR:        ^
+let c: b = 2;
+
+// CHECK:STDOUT: --- fail_aliased_name_in_diag.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.b = %b} [template]
+// CHECK:STDOUT:   %.loc7: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   %a: i32 = bind_name a, %.loc7
+// CHECK:STDOUT:   %a.ref: i32 = name_ref a, %a
+// CHECK:STDOUT:   %b: i32 = bind_alias b, %a
+// CHECK:STDOUT:   %b.ref: i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc12: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   %c: <error> = bind_name c, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 26 - 0
toolchain/check/testdata/alias/fail_bool_value.carbon

@@ -0,0 +1,26 @@
+// 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
+
+// CHECK:STDERR: fail_bool_value.carbon:[[@LINE+3]]:11: ERROR: Alias initializer must be a name reference.
+// CHECK:STDERR: alias a = false;
+// CHECK:STDERR:           ^~~~~
+alias a = false;
+let a_test: bool = a;
+
+// CHECK:STDOUT: --- fail_bool_value.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a} [template]
+// CHECK:STDOUT:   %.loc10: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   %a: <error> = bind_alias a, <error> [template = <error>]
+// CHECK:STDOUT:   %a.ref: <error> = name_ref a, %a [template = <error>]
+// CHECK:STDOUT:   %a_test: bool = bind_name a_test, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 29 - 0
toolchain/check/testdata/alias/fail_control_flow.carbon

@@ -0,0 +1,29 @@
+// 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
+
+// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+9]]:11: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
+// CHECK:STDERR: alias a = true or false;
+// CHECK:STDERR:           ^~~~~~~
+// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+6]]:11: ERROR: Semantics TODO: `Control flow expressions are currently only supported inside functions.`.
+// CHECK:STDERR: alias a = true or false;
+// CHECK:STDERR:           ^~~~~~~~~~~~~
+// CHECK:STDERR: fail_control_flow.carbon:[[@LINE+3]]:11: ERROR: Alias initializer must be a name reference.
+// CHECK:STDERR: alias a = true or false;
+// CHECK:STDERR:           ^~~~~~~~~~~~~
+alias a = true or false;
+
+// CHECK:STDOUT: --- fail_control_flow.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal true [template]
+// CHECK:STDOUT:   %.2: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %.loc16: bool = block_arg <unexpected instblockref block4>
+// CHECK:STDOUT:   %a: <error> = bind_alias a, <error> [template = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 43 - 0
toolchain/check/testdata/alias/fail_local_in_namespace.carbon

@@ -0,0 +1,43 @@
+// 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
+
+namespace NS;
+
+fn F() -> bool {
+  // CHECK:STDERR: fail_local_in_namespace.carbon:[[@LINE+6]]:9: ERROR: Name `NS` not found.
+  // CHECK:STDERR:   alias NS.a = false;
+  // CHECK:STDERR:         ^~
+  // CHECK:STDERR: fail_local_in_namespace.carbon:[[@LINE+3]]:16: ERROR: Alias initializer must be a name reference.
+  // CHECK:STDERR:   alias NS.a = false;
+  // CHECK:STDERR:                ^~~~~
+  alias NS.a = false;
+  // CHECK:STDERR: fail_local_in_namespace.carbon:[[@LINE+3]]:10: ERROR: Name `a` not found.
+  // CHECK:STDERR:   return NS.a;
+  // CHECK:STDERR:          ^~~~
+  return NS.a;
+}
+
+// CHECK:STDOUT: --- fail_local_in_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc7, .F = %F} [template]
+// CHECK:STDOUT:   %.loc7: <namespace> = namespace {} [template]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc16_16: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   %.loc16_9: <error> = bind_alias <invalid>, <error> [template = <error>]
+// CHECK:STDOUT:   %NS.ref: <namespace> = name_ref NS, file.%.loc7 [template = file.%.loc7]
+// CHECK:STDOUT:   %a.ref: <error> = name_ref a, <error> [template = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 42 - 0
toolchain/check/testdata/alias/fail_modifiers.carbon

@@ -0,0 +1,42 @@
+// 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
+
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+21]]:1: ERROR: `abstract` not allowed on `alias` declaration.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+18]]:10: ERROR: `base` not allowed on declaration with `abstract`.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR:          ^~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+15]]:1: `abstract` previously appeared here.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+12]]:15: ERROR: `default` not allowed on declaration with `abstract`.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR:               ^~~~~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+9]]:1: `abstract` previously appeared here.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR: ^~~~~~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+6]]:23: ERROR: `final` not allowed on declaration with `abstract`.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR:                       ^~~~~
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: `abstract` previously appeared here.
+// CHECK:STDERR: abstract base default final alias A = i32;
+// CHECK:STDERR: ^~~~~~~~
+abstract base default final alias A = i32;
+
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+3]]:1: ERROR: `impl` not allowed on `alias` declaration.
+// CHECK:STDERR: impl alias B = i32;
+// CHECK:STDERR: ^~~~
+impl alias B = i32;
+
+// CHECK:STDOUT: --- fail_modifiers.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A, .B = %B} [template]
+// CHECK:STDOUT:   %A: type = bind_alias A, i32 [template = i32]
+// CHECK:STDOUT:   %B: type = bind_alias B, i32 [template = i32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 50 - 0
toolchain/check/testdata/alias/fail_name_conflict.carbon

@@ -0,0 +1,50 @@
+// 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
+
+alias a = i32;
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE+6]]:5: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: var a: i32 = 1;
+// CHECK:STDERR:     ^
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE-4]]:7: Name is previously declared here.
+// CHECK:STDERR: alias a = i32;
+// CHECK:STDERR:       ^
+var a: i32 = 1;
+
+var b: i32 = 2;
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE+6]]:7: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: alias b = i32;
+// CHECK:STDERR:       ^
+// CHECK:STDERR: fail_name_conflict.carbon:[[@LINE-4]]:5: Name is previously declared here.
+// CHECK:STDERR: var b: i32 = 2;
+// CHECK:STDERR:     ^
+alias b = i32;
+
+// CHECK:STDOUT: --- fail_name_conflict.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a.loc7, .b = %b} [template]
+// CHECK:STDOUT:   %a.loc7: type = bind_alias a, i32 [template = i32]
+// CHECK:STDOUT:   %a.var: ref i32 = var a
+// CHECK:STDOUT:   %a.loc14: ref i32 = bind_name a, %a.var
+// CHECK:STDOUT:   %b.var: ref i32 = var b
+// CHECK:STDOUT:   %b: ref i32 = bind_name b, %b.var
+// CHECK:STDOUT:   %.loc23: type = bind_alias <invalid>, i32 [template = i32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc14: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   assign file.%a.var, %.loc14
+// CHECK:STDOUT:   %.loc16: i32 = int_literal 2 [template = constants.%.2]
+// CHECK:STDOUT:   assign file.%b.var, %.loc16
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 46 - 0
toolchain/check/testdata/alias/fail_not_constant.carbon

@@ -0,0 +1,46 @@
+// 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
+
+fn F() {
+  var a: i32 = 0;
+  var b: i32* = &a;
+  // CHECK:STDERR: fail_not_constant.carbon:[[@LINE+3]]:13: ERROR: Alias initializer must be a name reference.
+  // CHECK:STDERR:   alias c = *b;
+  // CHECK:STDERR:             ^~
+  alias c = *b;
+}
+
+// CHECK:STDOUT: --- fail_not_constant.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT:   %.2: type = ptr_type i32 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F} [template]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.var: ref i32 = var a
+// CHECK:STDOUT:   %a: ref i32 = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc8: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   assign %a.var, %.loc8
+// CHECK:STDOUT:   %.loc9_13: type = ptr_type i32 [template = constants.%.2]
+// CHECK:STDOUT:   %b.var: ref i32* = var b
+// CHECK:STDOUT:   %b: ref i32* = bind_name b, %b.var
+// CHECK:STDOUT:   %a.ref: ref i32 = name_ref a, %a
+// CHECK:STDOUT:   %.loc9_17: i32* = addr_of %a.ref
+// CHECK:STDOUT:   assign %b.var, %.loc9_17
+// CHECK:STDOUT:   %b.ref: ref i32* = name_ref b, %b
+// CHECK:STDOUT:   %.loc13_14: i32* = bind_value %b.ref
+// CHECK:STDOUT:   %.loc13_13: ref i32 = deref %.loc13_14
+// CHECK:STDOUT:   %c: <error> = bind_alias c, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 18 - 0
toolchain/check/testdata/alias/fail_todo_private.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+// CHECK:STDERR: fail_todo_private.carbon:[[@LINE+3]]:1: ERROR: Semantics TODO: `access modifier`.
+// CHECK:STDERR: private alias A = i32;
+// CHECK:STDERR: ^~~~~~~
+private alias A = i32;
+
+// CHECK:STDOUT: --- fail_todo_private.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A} [template]
+// CHECK:STDOUT:   %A: type = bind_alias A, i32 [template = i32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 61 - 0
toolchain/check/testdata/alias/import.carbon

@@ -0,0 +1,61 @@
+// 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
+
+// --- a.carbon
+
+library "a" api;
+
+var a: i32 = 0;
+
+alias b = a;
+
+// --- b.carbon
+
+library "b" api;
+
+import library "a";
+
+var c: i32 = b;
+
+// CHECK:STDOUT: --- a.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, .b = %b} [template]
+// CHECK:STDOUT:   %a.var: ref i32 = var a
+// CHECK:STDOUT:   %a: ref i32 = bind_name a, %a.var
+// CHECK:STDOUT:   %a.ref: ref i32 = name_ref a, %a
+// CHECK:STDOUT:   %b: ref i32 = bind_alias b, %a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc4: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   assign file.%a.var, %.loc4
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- b.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %import_ref.1, .b = %import_ref.2, .c = %c} [template]
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+2, unused
+// CHECK:STDOUT:   %import_ref.2: ref i32 = import_ref ir1, inst+7, used
+// CHECK:STDOUT:   %c.var: ref i32 = var c
+// CHECK:STDOUT:   %c: ref i32 = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.ref: ref i32 = name_ref b, file.%import_ref.2
+// CHECK:STDOUT:   %.loc6: i32 = bind_value %b.ref
+// CHECK:STDOUT:   assign file.%c.var, %.loc6
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 88 - 0
toolchain/check/testdata/alias/import_order.carbon

@@ -0,0 +1,88 @@
+// 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
+
+// --- a.carbon
+
+library "a" api;
+
+alias a = i32;
+alias b = a;
+alias c = b;
+alias d = c;
+
+// --- b.carbon
+
+library "b" api;
+
+import library "a";
+
+// Access imports in reverse order of export.
+var d_val: d = 1;
+var c_val: c = d_val;
+var b_val: b = c_val;
+var a_val: a = b_val;
+var i32_val: i32 = a_val;
+
+// CHECK:STDOUT: --- a.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, .b = %b, .c = %c, .d = %d} [template]
+// CHECK:STDOUT:   %a: type = bind_alias a, i32 [template = i32]
+// CHECK:STDOUT:   %a.ref: type = name_ref a, %a [template = i32]
+// CHECK:STDOUT:   %b: type = bind_alias b, %a [template = i32]
+// CHECK:STDOUT:   %b.ref: type = name_ref b, %b [template = i32]
+// CHECK:STDOUT:   %c: type = bind_alias c, %b [template = i32]
+// CHECK:STDOUT:   %c.ref: type = name_ref c, %c [template = i32]
+// CHECK:STDOUT:   %d: type = bind_alias d, %c [template = i32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- b.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %import_ref.1, .b = %import_ref.2, .c = %import_ref.3, .d = %import_ref.4, .d_val = %d_val, .c_val = %c_val, .b_val = %b_val, .a_val = %a_val, .i32_val = %i32_val} [template]
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, used [template = i32]
+// CHECK:STDOUT:   %import_ref.2: type = import_ref ir1, inst+3, used [template = i32]
+// CHECK:STDOUT:   %import_ref.3: type = import_ref ir1, inst+5, used [template = i32]
+// CHECK:STDOUT:   %import_ref.4: type = import_ref ir1, inst+7, used [template = i32]
+// CHECK:STDOUT:   %d.ref: type = name_ref d, %import_ref.4 [template = i32]
+// CHECK:STDOUT:   %d_val.var: ref i32 = var d_val
+// CHECK:STDOUT:   %d_val: ref i32 = bind_name d_val, %d_val.var
+// CHECK:STDOUT:   %c.ref: type = name_ref c, %import_ref.3 [template = i32]
+// CHECK:STDOUT:   %c_val.var: ref i32 = var c_val
+// CHECK:STDOUT:   %c_val: ref i32 = bind_name c_val, %c_val.var
+// CHECK:STDOUT:   %b.ref: type = name_ref b, %import_ref.2 [template = i32]
+// CHECK:STDOUT:   %b_val.var: ref i32 = var b_val
+// CHECK:STDOUT:   %b_val: ref i32 = bind_name b_val, %b_val.var
+// CHECK:STDOUT:   %a.ref: type = name_ref a, %import_ref.1 [template = i32]
+// CHECK:STDOUT:   %a_val.var: ref i32 = var a_val
+// CHECK:STDOUT:   %a_val: ref i32 = bind_name a_val, %a_val.var
+// CHECK:STDOUT:   %i32_val.var: ref i32 = var i32_val
+// CHECK:STDOUT:   %i32_val: ref i32 = bind_name i32_val, %i32_val.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7: i32 = int_literal 1 [template = constants.%.1]
+// CHECK:STDOUT:   assign file.%d_val.var, %.loc7
+// CHECK:STDOUT:   %d_val.ref: ref i32 = name_ref d_val, file.%d_val
+// CHECK:STDOUT:   %.loc8: i32 = bind_value %d_val.ref
+// CHECK:STDOUT:   assign file.%c_val.var, %.loc8
+// CHECK:STDOUT:   %c_val.ref: ref i32 = name_ref c_val, file.%c_val
+// CHECK:STDOUT:   %.loc9: i32 = bind_value %c_val.ref
+// CHECK:STDOUT:   assign file.%b_val.var, %.loc9
+// CHECK:STDOUT:   %b_val.ref: ref i32 = name_ref b_val, file.%b_val
+// CHECK:STDOUT:   %.loc10: i32 = bind_value %b_val.ref
+// CHECK:STDOUT:   assign file.%a_val.var, %.loc10
+// CHECK:STDOUT:   %a_val.ref: ref i32 = name_ref a_val, file.%a_val
+// CHECK:STDOUT:   %.loc11: i32 = bind_value %a_val.ref
+// CHECK:STDOUT:   assign file.%i32_val.var, %.loc11
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 38 - 0
toolchain/check/testdata/alias/in_namespace.carbon

@@ -0,0 +1,38 @@
+// 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
+
+namespace NS;
+alias NS.a = bool;
+
+let b: NS.a = false;
+
+fn F() -> NS.a {
+  return false;
+}
+
+// CHECK:STDOUT: --- in_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc7, .F = %F} [template]
+// CHECK:STDOUT:   %.loc7: <namespace> = namespace {.a = %a} [template]
+// CHECK:STDOUT:   %a: type = bind_alias a, bool [template = bool]
+// CHECK:STDOUT:   %NS.ref: <namespace> = name_ref NS, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %a.ref: type = name_ref a, %a [template = bool]
+// CHECK:STDOUT:   %.loc10: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   %b: bool = bind_name b, %.loc10
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> bool {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc13: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   return %.loc13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 36 - 0
toolchain/check/testdata/alias/local.carbon

@@ -0,0 +1,36 @@
+// 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
+
+fn F() -> i32 {
+  var a: i32 = 0;
+  alias b = a;
+  return b;
+}
+
+// CHECK:STDOUT: --- local.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F} [template]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.var: ref i32 = var a
+// CHECK:STDOUT:   %a: ref i32 = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc8: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   assign %a.var, %.loc8
+// CHECK:STDOUT:   %a.ref: ref i32 = name_ref a, %a
+// CHECK:STDOUT:   %b: ref i32 = bind_alias b, %a
+// CHECK:STDOUT:   %b.ref: ref i32 = name_ref b, %b
+// CHECK:STDOUT:   %.loc10: i32 = bind_value %b.ref
+// CHECK:STDOUT:   return %.loc10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 34 - 19
toolchain/check/testdata/class/fail_method.carbon

@@ -9,6 +9,8 @@ class Class {
   fn WithSelf[self: Class]();
 }
 
+alias A = Class.WithSelf;
+
 fn F(c: Class) {
   c.NoSelf();
   c.WithSelf();
@@ -17,17 +19,25 @@ fn F(c: Class) {
   // CHECK:STDERR: fail_method.carbon:[[@LINE+6]]:3: ERROR: Missing object argument in method call.
   // CHECK:STDERR:   Class.WithSelf();
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_method.carbon:[[@LINE-11]]:3: Calling function declared here.
+  // CHECK:STDERR: fail_method.carbon:[[@LINE-13]]:3: Calling function declared here.
   // CHECK:STDERR:   fn WithSelf[self: Class]();
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   Class.WithSelf();
   // CHECK:STDERR: fail_method.carbon:[[@LINE+6]]:3: ERROR: 1 argument(s) passed to function expecting 0 argument(s).
   // CHECK:STDERR:   Class.WithSelf(c);
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_method.carbon:[[@LINE-18]]:3: Calling function declared here.
+  // CHECK:STDERR: fail_method.carbon:[[@LINE-20]]:3: Calling function declared here.
   // CHECK:STDERR:   fn WithSelf[self: Class]();
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   Class.WithSelf(c);
+
+  // CHECK:STDERR: fail_method.carbon:[[@LINE+6]]:3: ERROR: Missing object argument in method call.
+  // CHECK:STDERR:   A();
+  // CHECK:STDERR:   ^~
+  // CHECK:STDERR: fail_method.carbon:[[@LINE-28]]:3: Calling function declared here.
+  // CHECK:STDERR:   fn WithSelf[self: Class]();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+  A();
 }
 
 // CHECK:STDOUT: --- fail_method.carbon
@@ -40,8 +50,11 @@ fn F(c: Class) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.Class = %Class.decl, .F = %F} [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.Class = %Class.decl, .A = %A, .F = %F} [template]
 // CHECK:STDOUT:   %Class.decl = class_decl @Class, ()
+// CHECK:STDOUT:   %Class.ref: type = name_ref Class, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %WithSelf.ref: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
+// CHECK:STDOUT:   %A: <function> = bind_alias A, @Class.%WithSelf [template = @Class.%WithSelf]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -60,22 +73,24 @@ fn F(c: Class) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%c: Class) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %c.ref.loc13: Class = name_ref c, %c
-// CHECK:STDOUT:   %NoSelf.ref.loc13: <function> = name_ref NoSelf, @Class.%NoSelf [template = @Class.%NoSelf]
-// CHECK:STDOUT:   %.loc13: init () = call %NoSelf.ref.loc13()
-// CHECK:STDOUT:   %c.ref.loc14: Class = name_ref c, %c
-// CHECK:STDOUT:   %.loc14_4: <bound method> = bound_method %c.ref.loc14, @Class.%WithSelf
-// CHECK:STDOUT:   %.loc14_13: init () = call %.loc14_4(%c.ref.loc14)
-// CHECK:STDOUT:   %Class.ref.loc16: type = name_ref Class, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:   %NoSelf.ref.loc16: <function> = name_ref NoSelf, @Class.%NoSelf [template = @Class.%NoSelf]
-// CHECK:STDOUT:   %.loc16: init () = call %NoSelf.ref.loc16()
-// CHECK:STDOUT:   %Class.ref.loc23: type = name_ref Class, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:   %WithSelf.ref.loc23: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
-// CHECK:STDOUT:   %.loc23: init () = call %WithSelf.ref.loc23(<invalid>)
-// CHECK:STDOUT:   %Class.ref.loc30: type = name_ref Class, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:   %WithSelf.ref.loc30: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
-// CHECK:STDOUT:   %c.ref.loc30: Class = name_ref c, %c
-// CHECK:STDOUT:   %.loc30: init () = call %WithSelf.ref.loc30(<invalid>)
+// CHECK:STDOUT:   %c.ref.loc15: Class = name_ref c, %c
+// CHECK:STDOUT:   %NoSelf.ref.loc15: <function> = name_ref NoSelf, @Class.%NoSelf [template = @Class.%NoSelf]
+// CHECK:STDOUT:   %.loc15: init () = call %NoSelf.ref.loc15()
+// CHECK:STDOUT:   %c.ref.loc16: Class = name_ref c, %c
+// CHECK:STDOUT:   %.loc16_4: <bound method> = bound_method %c.ref.loc16, @Class.%WithSelf
+// CHECK:STDOUT:   %.loc16_13: init () = call %.loc16_4(%c.ref.loc16)
+// CHECK:STDOUT:   %Class.ref.loc18: type = name_ref Class, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %NoSelf.ref.loc18: <function> = name_ref NoSelf, @Class.%NoSelf [template = @Class.%NoSelf]
+// CHECK:STDOUT:   %.loc18: init () = call %NoSelf.ref.loc18()
+// CHECK:STDOUT:   %Class.ref.loc25: type = name_ref Class, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %WithSelf.ref.loc25: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
+// CHECK:STDOUT:   %.loc25: init () = call %WithSelf.ref.loc25(<invalid>)
+// CHECK:STDOUT:   %Class.ref.loc32: type = name_ref Class, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %WithSelf.ref.loc32: <function> = name_ref WithSelf, @Class.%WithSelf [template = @Class.%WithSelf]
+// CHECK:STDOUT:   %c.ref.loc32: Class = name_ref c, %c
+// CHECK:STDOUT:   %.loc32: init () = call %WithSelf.ref.loc32(<invalid>)
+// CHECK:STDOUT:   %A.ref: <function> = name_ref A, file.%A [template = @Class.%WithSelf]
+// CHECK:STDOUT:   %.loc40: init () = call %A.ref(<invalid>)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 83 - 63
toolchain/check/testdata/class/method.carbon

@@ -8,6 +8,8 @@ class Class {
   fn F[self: Class]() -> i32;
   fn G[addr self: Class*]() -> i32;
 
+  alias A = F;
+
   var k: i32;
 }
 
@@ -21,6 +23,10 @@ fn Call(c: Class) -> i32 {
   return c.F();
 }
 
+fn CallAlias(c: Class) -> i32 {
+  return c.A();
+}
+
 fn CallOnConstBoundMethod() -> i32 {
   return ({.k = 1} as Class).F();
 }
@@ -61,10 +67,11 @@ fn CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.Class = %Class.decl, .Call = %Call, .CallOnConstBoundMethod = %CallOnConstBoundMethod, .CallWithAddr = %CallWithAddr, .CallFThroughPointer = %CallFThroughPointer, .CallGThroughPointer = %CallGThroughPointer, .Make = %Make, .CallFOnInitializingExpr = %CallFOnInitializingExpr, .CallGOnInitializingExpr = %CallGOnInitializingExpr} [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.Class = %Class.decl, .Call = %Call, .CallAlias = %CallAlias, .CallOnConstBoundMethod = %CallOnConstBoundMethod, .CallWithAddr = %CallWithAddr, .CallFThroughPointer = %CallFThroughPointer, .CallGThroughPointer = %CallGThroughPointer, .Make = %Make, .CallFOnInitializingExpr = %CallFOnInitializingExpr, .CallGOnInitializingExpr = %CallGOnInitializingExpr} [template]
 // CHECK:STDOUT:   %Class.decl = class_decl @Class, ()
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
 // CHECK:STDOUT:   %Call: <function> = fn_decl @Call [template]
+// CHECK:STDOUT:   %CallAlias: <function> = fn_decl @CallAlias [template]
 // CHECK:STDOUT:   %CallOnConstBoundMethod: <function> = fn_decl @CallOnConstBoundMethod [template]
 // CHECK:STDOUT:   %CallWithAddr: <function> = fn_decl @CallWithAddr [template]
 // CHECK:STDOUT:   %CallFThroughPointer: <function> = fn_decl @CallFThroughPointer [template]
@@ -77,20 +84,23 @@ fn CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: class @Class {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
 // CHECK:STDOUT:   %G: <function> = fn_decl @G [template]
-// CHECK:STDOUT:   %.loc11: <unbound element of class Class> = field_decl k, element0 [template]
+// CHECK:STDOUT:   %F.ref: <function> = name_ref F, %F [template = %F]
+// CHECK:STDOUT:   %A: <function> = bind_alias A, %F [template = %F]
+// CHECK:STDOUT:   %.loc13: <unbound element of class Class> = field_decl k, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F
 // CHECK:STDOUT:   .G = %G
-// CHECK:STDOUT:   .k = %.loc11
+// CHECK:STDOUT:   .A = %A
+// CHECK:STDOUT:   .k = %.loc13
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F[%self: Class]() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: Class = name_ref self, %self
-// CHECK:STDOUT:   %.loc15_14.1: ref i32 = class_element_access %self.ref, element0
-// CHECK:STDOUT:   %.loc15_14.2: i32 = bind_value %.loc15_14.1
-// CHECK:STDOUT:   return %.loc15_14.2
+// CHECK:STDOUT:   %.loc17_14.1: ref i32 = class_element_access %self.ref, element0
+// CHECK:STDOUT:   %.loc17_14.2: i32 = bind_value %.loc17_14.1
+// CHECK:STDOUT:   return %.loc17_14.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @G[addr %self: Class*]() -> i32;
@@ -98,30 +108,40 @@ fn CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: fn @Call(%c: Class) -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %c.ref: Class = name_ref c, %c
-// CHECK:STDOUT:   %.loc21_11: <bound method> = bound_method %c.ref, @Class.%F
-// CHECK:STDOUT:   %.loc21_13.1: init i32 = call %.loc21_11(%c.ref)
-// CHECK:STDOUT:   %.loc21_15: i32 = value_of_initializer %.loc21_13.1
-// CHECK:STDOUT:   %.loc21_13.2: i32 = converted %.loc21_13.1, %.loc21_15
-// CHECK:STDOUT:   return %.loc21_13.2
+// CHECK:STDOUT:   %.loc23_11: <bound method> = bound_method %c.ref, @Class.%F
+// CHECK:STDOUT:   %.loc23_13.1: init i32 = call %.loc23_11(%c.ref)
+// CHECK:STDOUT:   %.loc23_15: i32 = value_of_initializer %.loc23_13.1
+// CHECK:STDOUT:   %.loc23_13.2: i32 = converted %.loc23_13.1, %.loc23_15
+// CHECK:STDOUT:   return %.loc23_13.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallAlias(%c: Class) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %c.ref: Class = name_ref c, %c
+// CHECK:STDOUT:   %.loc27_11: <bound method> = bound_method %c.ref, @Class.%A
+// CHECK:STDOUT:   %.loc27_13.1: init i32 = call %.loc27_11(%c.ref)
+// CHECK:STDOUT:   %.loc27_15: i32 = value_of_initializer %.loc27_13.1
+// CHECK:STDOUT:   %.loc27_13.2: i32 = converted %.loc27_13.1, %.loc27_15
+// CHECK:STDOUT:   return %.loc27_13.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallOnConstBoundMethod() -> i32 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc25_17: i32 = int_literal 1 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc25_18.1: {.k: i32} = struct_literal (%.loc25_17)
+// CHECK:STDOUT:   %.loc31_17: i32 = int_literal 1 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc31_18.1: {.k: i32} = struct_literal (%.loc31_17)
 // CHECK:STDOUT:   %Class.ref: type = name_ref Class, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:   %.loc25_18.2: ref Class = temporary_storage
-// CHECK:STDOUT:   %.loc25_18.3: ref i32 = class_element_access %.loc25_18.2, element0
-// CHECK:STDOUT:   %.loc25_18.4: init i32 = initialize_from %.loc25_17 to %.loc25_18.3 [template = constants.%.5]
-// CHECK:STDOUT:   %.loc25_18.5: init Class = class_init (%.loc25_18.4), %.loc25_18.2 [template = constants.%.6]
-// CHECK:STDOUT:   %.loc25_18.6: ref Class = temporary %.loc25_18.2, %.loc25_18.5
-// CHECK:STDOUT:   %.loc25_18.7: ref Class = converted %.loc25_18.1, %.loc25_18.6
-// CHECK:STDOUT:   %.loc25_29: <bound method> = bound_method %.loc25_18.7, @Class.%F
-// CHECK:STDOUT:   %.loc25_18.8: Class = bind_value %.loc25_18.7
-// CHECK:STDOUT:   %.loc25_31.1: init i32 = call %.loc25_29(%.loc25_18.8)
-// CHECK:STDOUT:   %.loc25_33: i32 = value_of_initializer %.loc25_31.1
-// CHECK:STDOUT:   %.loc25_31.2: i32 = converted %.loc25_31.1, %.loc25_33
-// CHECK:STDOUT:   return %.loc25_31.2
+// CHECK:STDOUT:   %.loc31_18.2: ref Class = temporary_storage
+// CHECK:STDOUT:   %.loc31_18.3: ref i32 = class_element_access %.loc31_18.2, element0
+// CHECK:STDOUT:   %.loc31_18.4: init i32 = initialize_from %.loc31_17 to %.loc31_18.3 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc31_18.5: init Class = class_init (%.loc31_18.4), %.loc31_18.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc31_18.6: ref Class = temporary %.loc31_18.2, %.loc31_18.5
+// CHECK:STDOUT:   %.loc31_18.7: ref Class = converted %.loc31_18.1, %.loc31_18.6
+// CHECK:STDOUT:   %.loc31_29: <bound method> = bound_method %.loc31_18.7, @Class.%F
+// CHECK:STDOUT:   %.loc31_18.8: Class = bind_value %.loc31_18.7
+// CHECK:STDOUT:   %.loc31_31.1: init i32 = call %.loc31_29(%.loc31_18.8)
+// CHECK:STDOUT:   %.loc31_33: i32 = value_of_initializer %.loc31_31.1
+// CHECK:STDOUT:   %.loc31_31.2: i32 = converted %.loc31_31.1, %.loc31_33
+// CHECK:STDOUT:   return %.loc31_31.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallWithAddr() -> i32 {
@@ -130,36 +150,36 @@ fn CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT:   %c.var: ref Class = var c
 // CHECK:STDOUT:   %c: ref Class = bind_name c, %c.var
 // CHECK:STDOUT:   %c.ref: ref Class = name_ref c, %c
-// CHECK:STDOUT:   %.loc30_11: <bound method> = bound_method %c.ref, @Class.%G
-// CHECK:STDOUT:   %.loc30_10: Class* = addr_of %c.ref
-// CHECK:STDOUT:   %.loc30_13.1: init i32 = call %.loc30_11(%.loc30_10)
-// CHECK:STDOUT:   %.loc30_15: i32 = value_of_initializer %.loc30_13.1
-// CHECK:STDOUT:   %.loc30_13.2: i32 = converted %.loc30_13.1, %.loc30_15
-// CHECK:STDOUT:   return %.loc30_13.2
+// CHECK:STDOUT:   %.loc36_11: <bound method> = bound_method %c.ref, @Class.%G
+// CHECK:STDOUT:   %.loc36_10: Class* = addr_of %c.ref
+// CHECK:STDOUT:   %.loc36_13.1: init i32 = call %.loc36_11(%.loc36_10)
+// CHECK:STDOUT:   %.loc36_15: i32 = value_of_initializer %.loc36_13.1
+// CHECK:STDOUT:   %.loc36_13.2: i32 = converted %.loc36_13.1, %.loc36_15
+// CHECK:STDOUT:   return %.loc36_13.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallFThroughPointer(%p: Class*) -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: Class* = name_ref p, %p
-// CHECK:STDOUT:   %.loc34_11.1: ref Class = deref %p.ref
-// CHECK:STDOUT:   %.loc34_14: <bound method> = bound_method %.loc34_11.1, @Class.%F
-// CHECK:STDOUT:   %.loc34_11.2: Class = bind_value %.loc34_11.1
-// CHECK:STDOUT:   %.loc34_16.1: init i32 = call %.loc34_14(%.loc34_11.2)
-// CHECK:STDOUT:   %.loc34_18: i32 = value_of_initializer %.loc34_16.1
-// CHECK:STDOUT:   %.loc34_16.2: i32 = converted %.loc34_16.1, %.loc34_18
-// CHECK:STDOUT:   return %.loc34_16.2
+// CHECK:STDOUT:   %.loc40_11.1: ref Class = deref %p.ref
+// CHECK:STDOUT:   %.loc40_14: <bound method> = bound_method %.loc40_11.1, @Class.%F
+// CHECK:STDOUT:   %.loc40_11.2: Class = bind_value %.loc40_11.1
+// CHECK:STDOUT:   %.loc40_16.1: init i32 = call %.loc40_14(%.loc40_11.2)
+// CHECK:STDOUT:   %.loc40_18: i32 = value_of_initializer %.loc40_16.1
+// CHECK:STDOUT:   %.loc40_16.2: i32 = converted %.loc40_16.1, %.loc40_18
+// CHECK:STDOUT:   return %.loc40_16.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallGThroughPointer(%p: Class*) -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %p.ref: Class* = name_ref p, %p
-// CHECK:STDOUT:   %.loc38_11.1: ref Class = deref %p.ref
-// CHECK:STDOUT:   %.loc38_14: <bound method> = bound_method %.loc38_11.1, @Class.%G
-// CHECK:STDOUT:   %.loc38_11.2: Class* = addr_of %.loc38_11.1
-// CHECK:STDOUT:   %.loc38_16.1: init i32 = call %.loc38_14(%.loc38_11.2)
-// CHECK:STDOUT:   %.loc38_18: i32 = value_of_initializer %.loc38_16.1
-// CHECK:STDOUT:   %.loc38_16.2: i32 = converted %.loc38_16.1, %.loc38_18
-// CHECK:STDOUT:   return %.loc38_16.2
+// CHECK:STDOUT:   %.loc44_11.1: ref Class = deref %p.ref
+// CHECK:STDOUT:   %.loc44_14: <bound method> = bound_method %.loc44_11.1, @Class.%G
+// CHECK:STDOUT:   %.loc44_11.2: Class* = addr_of %.loc44_11.1
+// CHECK:STDOUT:   %.loc44_16.1: init i32 = call %.loc44_14(%.loc44_11.2)
+// CHECK:STDOUT:   %.loc44_18: i32 = value_of_initializer %.loc44_16.1
+// CHECK:STDOUT:   %.loc44_16.2: i32 = converted %.loc44_16.1, %.loc44_18
+// CHECK:STDOUT:   return %.loc44_16.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return: Class;
@@ -167,28 +187,28 @@ fn CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: fn @CallFOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Make.ref: <function> = name_ref Make, file.%Make [template = file.%Make]
-// CHECK:STDOUT:   %.loc44_14.1: ref Class = temporary_storage
-// CHECK:STDOUT:   %.loc44_14.2: init Class = call %Make.ref() to %.loc44_14.1
-// CHECK:STDOUT:   %.loc44_14.3: ref Class = temporary %.loc44_14.1, %.loc44_14.2
-// CHECK:STDOUT:   %.loc44_16: <bound method> = bound_method %.loc44_14.3, @Class.%F
-// CHECK:STDOUT:   %.loc44_14.4: Class = bind_value %.loc44_14.3
-// CHECK:STDOUT:   %.loc44_18.1: init i32 = call %.loc44_16(%.loc44_14.4)
-// CHECK:STDOUT:   %.loc44_20: i32 = value_of_initializer %.loc44_18.1
-// CHECK:STDOUT:   %.loc44_18.2: i32 = converted %.loc44_18.1, %.loc44_20
-// CHECK:STDOUT:   return %.loc44_18.2
+// CHECK:STDOUT:   %.loc50_14.1: ref Class = temporary_storage
+// CHECK:STDOUT:   %.loc50_14.2: init Class = call %Make.ref() to %.loc50_14.1
+// CHECK:STDOUT:   %.loc50_14.3: ref Class = temporary %.loc50_14.1, %.loc50_14.2
+// CHECK:STDOUT:   %.loc50_16: <bound method> = bound_method %.loc50_14.3, @Class.%F
+// CHECK:STDOUT:   %.loc50_14.4: Class = bind_value %.loc50_14.3
+// CHECK:STDOUT:   %.loc50_18.1: init i32 = call %.loc50_16(%.loc50_14.4)
+// CHECK:STDOUT:   %.loc50_20: i32 = value_of_initializer %.loc50_18.1
+// CHECK:STDOUT:   %.loc50_18.2: i32 = converted %.loc50_18.1, %.loc50_20
+// CHECK:STDOUT:   return %.loc50_18.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @CallGOnInitializingExpr() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Make.ref: <function> = name_ref Make, file.%Make [template = file.%Make]
-// CHECK:STDOUT:   %.loc48_14.1: ref Class = temporary_storage
-// CHECK:STDOUT:   %.loc48_14.2: init Class = call %Make.ref() to %.loc48_14.1
-// CHECK:STDOUT:   %.loc48_14.3: ref Class = temporary %.loc48_14.1, %.loc48_14.2
-// CHECK:STDOUT:   %.loc48_16: <bound method> = bound_method %.loc48_14.3, @Class.%G
-// CHECK:STDOUT:   %.loc48_14.4: Class* = addr_of %.loc48_14.3
-// CHECK:STDOUT:   %.loc48_18.1: init i32 = call %.loc48_16(%.loc48_14.4)
-// CHECK:STDOUT:   %.loc48_20: i32 = value_of_initializer %.loc48_18.1
-// CHECK:STDOUT:   %.loc48_18.2: i32 = converted %.loc48_18.1, %.loc48_20
-// CHECK:STDOUT:   return %.loc48_18.2
+// CHECK:STDOUT:   %.loc54_14.1: ref Class = temporary_storage
+// CHECK:STDOUT:   %.loc54_14.2: init Class = call %Make.ref() to %.loc54_14.1
+// CHECK:STDOUT:   %.loc54_14.3: ref Class = temporary %.loc54_14.1, %.loc54_14.2
+// CHECK:STDOUT:   %.loc54_16: <bound method> = bound_method %.loc54_14.3, @Class.%G
+// CHECK:STDOUT:   %.loc54_14.4: Class* = addr_of %.loc54_14.3
+// CHECK:STDOUT:   %.loc54_18.1: init i32 = call %.loc54_16(%.loc54_14.4)
+// CHECK:STDOUT:   %.loc54_20: i32 = value_of_initializer %.loc54_18.1
+// CHECK:STDOUT:   %.loc54_18.2: i32 = converted %.loc54_18.1, %.loc54_20
+// CHECK:STDOUT:   return %.loc54_18.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 44 - 0
toolchain/check/testdata/function/call/alias.carbon

@@ -0,0 +1,44 @@
+// 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
+
+fn A() -> i32 { return 0; }
+
+alias B = A;
+
+fn Main() {
+  var b: i32 = B();
+}
+
+// CHECK:STDOUT: --- alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A, .B = %B, .Main = %Main} [template]
+// CHECK:STDOUT:   %A: <function> = fn_decl @A [template]
+// CHECK:STDOUT:   %A.ref: <function> = name_ref A, %A [template = %A]
+// CHECK:STDOUT:   %B: <function> = bind_alias B, %A [template = %A]
+// CHECK:STDOUT:   %Main: <function> = fn_decl @Main [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   return %.loc7
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Main() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %b.var: ref i32 = var b
+// CHECK:STDOUT:   %b: ref i32 = bind_name b, %b.var
+// CHECK:STDOUT:   %B.ref: <function> = name_ref B, file.%B [template = file.%A]
+// CHECK:STDOUT:   %.loc12: init i32 = call %B.ref()
+// CHECK:STDOUT:   assign %b.var, %.loc12
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 62 - 0
toolchain/check/testdata/namespace/alias.carbon

@@ -0,0 +1,62 @@
+// 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
+
+namespace NS;
+
+alias ns = NS;
+
+fn NS.A() -> i32 { return 0; }
+
+fn B() -> i32 { return ns.A(); }
+
+alias C = NS.A;
+
+fn D() -> i32 { return C(); }
+
+// CHECK:STDOUT: --- alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc7, .ns = %ns, .B = %B, .C = %C, .D = %D} [template]
+// CHECK:STDOUT:   %.loc7: <namespace> = namespace {.A = %A} [template]
+// CHECK:STDOUT:   %NS.ref.loc9: <namespace> = name_ref NS, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %ns: <namespace> = bind_alias ns, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %A: <function> = fn_decl @A [template]
+// CHECK:STDOUT:   %B: <function> = fn_decl @B [template]
+// CHECK:STDOUT:   %NS.ref.loc15: <namespace> = name_ref NS, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %A.ref: <function> = name_ref A, %A [template = %A]
+// CHECK:STDOUT:   %C: <function> = bind_alias C, %A [template = %A]
+// CHECK:STDOUT:   %D: <function> = fn_decl @D [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc11: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   return %.loc11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @B() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %ns.ref: <namespace> = name_ref ns, file.%ns [template = file.%.loc7]
+// CHECK:STDOUT:   %A.ref: <function> = name_ref A, file.%A [template = file.%A]
+// CHECK:STDOUT:   %.loc13_28.1: init i32 = call %A.ref()
+// CHECK:STDOUT:   %.loc13_30: i32 = value_of_initializer %.loc13_28.1
+// CHECK:STDOUT:   %.loc13_28.2: i32 = converted %.loc13_28.1, %.loc13_30
+// CHECK:STDOUT:   return %.loc13_28.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @D() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.ref: <function> = name_ref C, file.%C [template = file.%A]
+// CHECK:STDOUT:   %.loc17_25.1: init i32 = call %C.ref()
+// CHECK:STDOUT:   %.loc17_27: i32 = value_of_initializer %.loc17_25.1
+// CHECK:STDOUT:   %.loc17_25.2: i32 = converted %.loc17_25.1, %.loc17_27
+// CHECK:STDOUT:   return %.loc17_25.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 39 - 0
toolchain/check/testdata/namespace/fail_decl_in_alias.carbon

@@ -0,0 +1,39 @@
+// 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
+
+namespace NS;
+
+alias ns = NS;
+
+// Aliases can't be used when declaring names.
+// CHECK:STDERR: fail_decl_in_alias.carbon:[[@LINE+6]]:7: ERROR: Name qualifiers are only allowed for entities that provide a scope.
+// CHECK:STDERR: fn ns.A() -> i32 { return 0; }
+// CHECK:STDERR:       ^
+// CHECK:STDERR: fail_decl_in_alias.carbon:[[@LINE+3]]:4: Non-scope entity referenced here.
+// CHECK:STDERR: fn ns.A() -> i32 { return 0; }
+// CHECK:STDERR:    ^~
+fn ns.A() -> i32 { return 0; }
+
+// CHECK:STDOUT: --- fail_decl_in_alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc7, .ns = %ns} [template]
+// CHECK:STDOUT:   %.loc7: <namespace> = namespace {} [template]
+// CHECK:STDOUT:   %NS.ref: <namespace> = name_ref NS, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %ns: <namespace> = bind_alias ns, %.loc7 [template = %.loc7]
+// CHECK:STDOUT:   %.loc18: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc18: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   return %.loc18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 6 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -118,6 +118,9 @@ CARBON_DIAGNOSTIC_KIND(ExpectedAfterBase)
 CARBON_DIAGNOSTIC_KIND(ImplExpectedAfterForall)
 CARBON_DIAGNOSTIC_KIND(ImplExpectedAs)
 
+// Alias diagnostics.
+CARBON_DIAGNOSTIC_KIND(ExpectedAliasInitializer)
+
 // ============================================================================
 // Semantics diagnostics
 // ============================================================================
@@ -250,6 +253,9 @@ CARBON_DIAGNOSTIC_KIND(ModifierNotAllowedWith)
 CARBON_DIAGNOSTIC_KIND(ModifierMustAppearBefore)
 CARBON_DIAGNOSTIC_KIND(ModifierPrevious)
 
+// Alias diagnostics.
+CARBON_DIAGNOSTIC_KIND(AliasRequiresNameRef)
+
 // ============================================================================
 // Other diagnostics
 // ============================================================================

+ 10 - 0
toolchain/lower/handle.cpp

@@ -60,6 +60,16 @@ auto HandleAssign(FunctionContext& context, SemIR::InstId /*inst_id*/,
   context.FinishInit(storage_type_id, inst.lhs_id, inst.rhs_id);
 }
 
+auto HandleBindAlias(FunctionContext& context, SemIR::InstId inst_id,
+                     SemIR::BindAlias inst) -> void {
+  auto type_inst_id = context.sem_ir().types().GetInstId(inst.type_id);
+  if (type_inst_id == SemIR::InstId::BuiltinNamespaceType) {
+    return;
+  }
+
+  context.SetLocal(inst_id, context.GetValue(inst.value_id));
+}
+
 auto HandleBindName(FunctionContext& context, SemIR::InstId inst_id,
                     SemIR::BindName inst) -> void {
   context.SetLocal(inst_id, context.GetValue(inst.value_id));

+ 21 - 0
toolchain/lower/testdata/alias/local.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn F() -> i32 {
+  var a: i32 = 0;
+  alias b = a;
+  return b;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'local.carbon'
+// CHECK:STDOUT: source_filename = "local.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @F() {
+// CHECK:STDOUT:   %a = alloca i32, align 4
+// CHECK:STDOUT:   store i32 0, ptr %a, align 4
+// CHECK:STDOUT:   %1 = load i32, ptr %a, align 4
+// CHECK:STDOUT:   ret i32 %1
+// CHECK:STDOUT: }

+ 61 - 0
toolchain/parse/handle_alias.cpp

@@ -0,0 +1,61 @@
+// 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 "toolchain/lex/token_kind.h"
+#include "toolchain/lex/tokenized_buffer.h"
+#include "toolchain/parse/context.h"
+#include "toolchain/parse/node_kind.h"
+#include "toolchain/parse/state.h"
+
+namespace Carbon::Parse {
+
+auto HandleAlias(Context& context) -> void {
+  auto state = context.PopState();
+
+  context.PushState(state, State::AliasAfterName);
+  context.PushState(State::DeclNameAndParamsAsNone, state.token);
+}
+
+auto HandleAliasAfterName(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (state.has_error) {
+    context.RecoverFromDeclError(state, NodeKind::Alias,
+                                 /*skip_past_likely_end=*/true);
+    return;
+  }
+
+  if (auto equal = context.ConsumeIf(Lex::TokenKind::Equal)) {
+    context.AddLeafNode(NodeKind::AliasInitializer, *equal);
+    context.PushState(state, State::AliasFinish);
+    context.PushState(State::Expr);
+  } else {
+    CARBON_DIAGNOSTIC(ExpectedAliasInitializer, Error,
+                      "`alias` requires a `=` for the source.");
+    context.emitter().Emit(*context.position(), ExpectedAliasInitializer);
+    context.RecoverFromDeclError(state, NodeKind::Alias,
+                                 /*skip_past_likely_end=*/true);
+  }
+}
+
+auto HandleAliasFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (state.has_error) {
+    context.RecoverFromDeclError(state, NodeKind::Alias,
+                                 /*skip_past_likely_end=*/true);
+    return;
+  }
+
+  if (auto semi = context.ConsumeIf(Lex::TokenKind::Semi)) {
+    context.AddNode(NodeKind::Alias, *semi, state.subtree_start,
+                    state.has_error);
+  } else {
+    context.EmitExpectedDeclSemi(Lex::TokenKind::Alias);
+    context.RecoverFromDeclError(state, NodeKind::Alias,
+                                 /*skip_past_likely_end=*/true);
+  }
+}
+
+}  // namespace Carbon::Parse

+ 5 - 0
toolchain/parse/handle_decl_scope_loop.cpp

@@ -110,6 +110,10 @@ static auto HandleBaseAsDecl(Context& context, Context::StateStackEntry state)
 static auto TryHandleAsDecl(Context& context, Context::StateStackEntry state,
                             bool saw_modifier) -> bool {
   switch (context.PositionKind()) {
+    case Lex::TokenKind::Alias: {
+      ApplyIntroducer(context, state, NodeKind::AliasIntroducer, State::Alias);
+      return true;
+    }
     case Lex::TokenKind::Base: {
       HandleBaseAsDecl(context, state);
       return true;
@@ -195,6 +199,7 @@ static auto ResolveAmbiguousTokenAsDeclaration(Context& context,
       // also modifiers (such as `base`). Other introducer tokens need to be
       // added by hand.
       switch (context.PositionKind(Lookahead::NextToken)) {
+        case Lex::TokenKind::Alias:
         case Lex::TokenKind::Class:
         case Lex::TokenKind::Constraint:
         case Lex::TokenKind::Fn:

+ 5 - 0
toolchain/parse/handle_statement.cpp

@@ -10,6 +10,11 @@ auto HandleStatement(Context& context) -> void {
   context.PopAndDiscardState();
 
   switch (context.PositionKind()) {
+    case Lex::TokenKind::Alias: {
+      context.PushState(State::Alias);
+      context.AddLeafNode(NodeKind::AliasIntroducer, context.Consume());
+      break;
+    }
     case Lex::TokenKind::Break: {
       context.PushState(State::StatementBreakFinish);
       context.AddLeafNode(NodeKind::BreakStatementStart, context.Consume());

+ 11 - 0
toolchain/parse/node_kind.def

@@ -256,6 +256,17 @@ CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinition, FunctionDefinitionStart,
 CARBON_PARSE_NODE_KIND_BRACKET(FunctionDecl, FunctionIntroducer,
                                CARBON_IF_VALID(Semi))
 
+// `alias`:
+//   AliasIntroducer
+//   _repeated_ _external_: modifier
+//   _external_: IdentifierName or QualifiedName
+//   AliasInitializer
+//   _external_: expression
+// Alias
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(AliasIntroducer, 0, Alias)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(AliasInitializer, 0, Equal)
+CARBON_PARSE_NODE_KIND_BRACKET(Alias, AliasIntroducer, CARBON_IF_VALID(Semi))
+
 // A tuple pattern:
 //   TuplePatternStart
 //     _external_: [Generic]BindingPattern

+ 34 - 0
toolchain/parse/state.def

@@ -307,6 +307,11 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit)
 // ^~~~~~~
 //   1. DeclScopeLoop
 //
+// alias ...
+// ^~~~~
+//   1. Alias
+//   2. DeclScopeLoop
+//
 // class ...
 // ^~~~~
 //   1. TypeAfterIntroducerAsClass
@@ -672,6 +677,35 @@ CARBON_PARSE_STATE(NamespaceFinish)
 //   (state done)
 CARBON_PARSE_STATE(Package)
 
+// Handles `alias` after the introducer.
+//
+// alias ...
+//      ^
+//   1. DeclNameAndParamsAsNone
+//   2. AliasInitializer
+CARBON_PARSE_STATE(Alias)
+
+// Handles `alias` after the name.
+//
+// alias ... = ... ;
+//           ^
+//   1. Expr
+//   2. AliasFinish
+//
+// alias ???
+//       ^
+//   (state done)
+CARBON_PARSE_STATE(AliasAfterName)
+
+// Handles `alias` at the end.
+//
+// alias ... ;
+//           ^
+// alias ???
+//       ^
+//   (state done)
+CARBON_PARSE_STATE(AliasFinish)
+
 // Starts parsing a pattern in a comma-separated list. The variants mark
 // whether it is part of an implicit parameter list or a tuple pattern.
 //

+ 69 - 0
toolchain/parse/testdata/alias/basic.carbon

@@ -0,0 +1,69 @@
+// 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
+
+alias a = foo;
+alias b = 0;
+alias c = 1 + 3;
+
+alias NS.ns = foo;
+
+class C {
+  alias c = foo;
+}
+
+fn F() {
+  alias f = foo;
+}
+
+// CHECK:STDOUT: - filename: basic.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'foo'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'b'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'IntLiteral', text: '0'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'c'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'IntLiteral', text: '1'},
+// CHECK:STDOUT:         {kind: 'IntLiteral', text: '3'},
+// CHECK:STDOUT:       {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'NS'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'ns'},
+// CHECK:STDOUT:       {kind: 'QualifiedName', text: '.', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'foo'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', subtree_size: 7},
+// CHECK:STDOUT:         {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'C'},
+// CHECK:STDOUT:       {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'c'},
+// CHECK:STDOUT:         {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'foo'},
+// CHECK:STDOUT:       {kind: 'Alias', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'f'},
+// CHECK:STDOUT:         {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'foo'},
+// CHECK:STDOUT:       {kind: 'Alias', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 117 - 0
toolchain/parse/testdata/alias/fail_syntax.carbon

@@ -0,0 +1,117 @@
+// 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
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:7: ERROR: `alias` introducer should be followed by a name.
+// CHECK:STDERR: alias = b;
+// CHECK:STDERR:       ^
+alias = b;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:9: ERROR: `alias` requires a `=` for the source.
+// CHECK:STDERR: alias a b;
+// CHECK:STDERR:         ^
+alias a b;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:10: ERROR: Expected expression.
+// CHECK:STDERR: alias a =;
+// CHECK:STDERR:          ^
+alias a =;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:8: ERROR: `alias` requires a `=` for the source.
+// CHECK:STDERR: alias a;
+// CHECK:STDERR:        ^
+alias a;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:7: ERROR: `alias` introducer should be followed by a name.
+// CHECK:STDERR: alias =;
+// CHECK:STDERR:       ^
+alias =;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:9: ERROR: `alias` requires a `=` for the source.
+// CHECK:STDERR: alias a b c = d;
+// CHECK:STDERR:         ^
+alias a b c = d;
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:13: ERROR: `alias` declarations must end with a `;`.
+// CHECK:STDERR: alias a = b c d;
+// CHECK:STDERR:             ^
+alias a = b c d;
+
+fn F() {
+  // CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:8: ERROR: `alias` introducer should be followed by a name.
+  // CHECK:STDERR:   alias;
+  // CHECK:STDERR:        ^
+  alias;
+
+  // CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:12: ERROR: Expected expression.
+  // CHECK:STDERR:   alias a =;
+  // CHECK:STDERR:            ^
+  alias a =;
+
+  alias a =
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+3]]:1: ERROR: Expected expression.
+// CHECK:STDERR: }
+// CHECK:STDERR: ^
+}
+
+alias a = b
+
+// CHECK:STDERR: fail_syntax.carbon:[[@LINE+56]]:21: ERROR: `alias` declarations must end with a `;`.
+// CHECK:STDERR: // CHECK:STDOUT:   ]
+// CHECK:STDERR:                     ^
+// CHECK:STDOUT: - filename: fail_syntax.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '=', has_error: yes},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '=', has_error: yes},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'b'},
+// CHECK:STDOUT:     {kind: 'Alias', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TuplePattern', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'Alias', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:         {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'Alias', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:         {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'InvalidParse', text: '}', has_error: yes},
+// CHECK:STDOUT:       {kind: 'Alias', text: '=', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 19},
+// CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'a'},
+// CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'b'},
+// CHECK:STDOUT:     {kind: 'Alias', text: 'b', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 19 - 0
toolchain/parse/typed_nodes.h

@@ -312,6 +312,25 @@ struct FunctionDefinition {
   llvm::SmallVector<AnyStatementId> body;
 };
 
+// `alias` nodes
+// -------------
+
+using AliasIntroducer = LeafNode<NodeKind::AliasIntroducer>;
+using AliasInitializer = LeafNode<NodeKind::AliasInitializer>;
+
+// An `alias` declaration: `alias a = b;`.
+struct Alias {
+  static constexpr auto Kind =
+      NodeKind::Alias.Define(NodeCategory::Decl | NodeCategory::Statement);
+
+  AliasIntroducerId introducer;
+  llvm::SmallVector<AnyModifierId> modifiers;
+  // For now, this is either an IdentifierName or a QualifiedName.
+  AnyNameComponentId name;
+  AliasInitializerId equals;
+  AnyExprId initializer;
+};
+
 // `let` nodes
 // -----------
 

+ 8 - 2
toolchain/sem_ir/file.cpp

@@ -175,7 +175,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
                     for (int i : llvm::seq(start, insts_.size())) {
                       auto id = InstId(i);
                       auto value = constant_values_.Get(id);
-                      if (value.is_constant()) {
+                      if (!value.is_valid() || value.is_constant()) {
                         map.Add(PrintToString(id), Yaml::OutputScalar(value));
                       }
                     }
@@ -191,6 +191,7 @@ auto File::OutputYaml(bool include_builtins) const -> Yaml::OutputMapping {
 static auto GetTypePrecedence(InstKind kind) -> int {
   switch (kind) {
     case ArrayType::Kind:
+    case BindAlias::Kind:
     case BindSymbolicName::Kind:
     case Builtin::Kind:
     case ClassType::Kind:
@@ -315,8 +316,9 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
         }
         break;
       }
+      case BindAlias::Kind:
       case BindSymbolicName::Kind: {
-        auto name_id = inst.As<BindSymbolicName>().bind_name_id;
+        auto name_id = inst.As<AnyBindName>().bind_name_id;
         out << sem_ir.names().GetFormatted(
             sem_ir.bind_names().Get(name_id).name_id);
         break;
@@ -535,6 +537,10 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
         continue;
       }
 
+      case BindAlias::Kind: {
+        inst_id = inst.As<BindAlias>().value_id;
+        continue;
+      }
       case NameRef::Kind: {
         inst_id = inst.As<NameRef>().value_id;
         continue;

+ 1 - 0
toolchain/sem_ir/formatter.cpp

@@ -465,6 +465,7 @@ class InstNamer {
           CollectNamesInBlock(scope_id, inst.As<SpliceBlock>().block_id);
           break;
         }
+        case BindAlias::Kind:
         case BindName::Kind:
         case BindSymbolicName::Kind: {
           add_inst_name_id(sem_ir_.bind_names()

+ 2 - 1
toolchain/sem_ir/inst_kind.def

@@ -24,8 +24,9 @@ CARBON_SEM_IR_INST_KIND(ArrayInit)
 CARBON_SEM_IR_INST_KIND(ArrayType)
 CARBON_SEM_IR_INST_KIND(Assign)
 CARBON_SEM_IR_INST_KIND(BaseDecl)
-CARBON_SEM_IR_INST_KIND(BindSymbolicName)
+CARBON_SEM_IR_INST_KIND(BindAlias)
 CARBON_SEM_IR_INST_KIND(BindName)
+CARBON_SEM_IR_INST_KIND(BindSymbolicName)
 CARBON_SEM_IR_INST_KIND(BindValue)
 CARBON_SEM_IR_INST_KIND(BlockArg)
 CARBON_SEM_IR_INST_KIND(BoolLiteral)

+ 12 - 3
toolchain/sem_ir/typed_insts.h

@@ -169,7 +169,7 @@ struct BaseDecl {
 // Common representation for both kinds of `bind*name` node.
 struct AnyBindName {
   // TODO: Also handle BindTemplateName once it exists.
-  static constexpr InstKind Kinds[] = {InstKind::BindName,
+  static constexpr InstKind Kinds[] = {InstKind::BindAlias, InstKind::BindName,
                                        InstKind::BindSymbolicName};
 
   InstKind kind;
@@ -178,9 +178,9 @@ struct AnyBindName {
   InstId value_id;
 };
 
-struct BindSymbolicName {
+struct BindAlias {
   static constexpr auto Kind =
-      InstKind::BindSymbolicName.Define<Parse::NodeId>("bind_symbolic_name");
+      InstKind::BindAlias.Define<Parse::NodeId>("bind_alias");
 
   TypeId type_id;
   BindNameId bind_name_id;
@@ -199,6 +199,15 @@ struct BindName {
   InstId value_id;
 };
 
+struct BindSymbolicName {
+  static constexpr auto Kind =
+      InstKind::BindSymbolicName.Define<Parse::NodeId>("bind_symbolic_name");
+
+  TypeId type_id;
+  BindNameId bind_name_id;
+  InstId value_id;
+};
+
 struct BindValue {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind =