Procházet zdrojové kódy

Initial support for aliases. (#1262)

This supports aliases for types (including interfaces), functions, parameterized types, instance member names, and interface member names.

Co-authored-by: Jon Meow <jperkins@google.com>
Richard Smith před 4 roky
rodič
revize
50d0561249

+ 7 - 0
.pre-commit-config.yaml

@@ -135,6 +135,13 @@ repos:
     hooks:
       - id: codespell
         args: ['-I', '.codespell_ignore', '--uri-ignore-words-list', '*']
+        # Test data may contain intentional misspellings, as well as short,
+        # meaningless identifiers that codespell incorrectly identifies as
+        # typos but that we would want to detect in other contexts.
+        exclude: |
+          (?x)^(
+              .*/testdata/.*
+          )$
   - repo: https://github.com/google/pre-commit-tool-hooks
     rev: cb78d9293306d9f737c64d9702bbaa88e157caaa # frozen: v1.2.2
     hooks:

+ 6 - 0
common/fuzzing/carbon.proto

@@ -332,6 +332,11 @@ message ImplDeclaration {
   repeated Declaration members = 4;
 }
 
+message AliasDeclaration {
+  optional string name = 1;
+  optional Expression target = 2;
+}
+
 message Declaration {
   oneof kind {
     FunctionDeclaration function = 1;
@@ -340,6 +345,7 @@ message Declaration {
     VariableDeclaration variable = 4;
     InterfaceDeclaration interface = 5;
     ImplDeclaration impl = 6;
+    AliasDeclaration alias = 7;
   }
 }
 

+ 10 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -695,6 +695,16 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
       out << "}";
       break;
     }
+
+    case Fuzzing::Declaration::kAlias: {
+      const auto& alias = declaration.alias();
+      out << "alias ";
+      IdentifierToCarbon(alias.name(), out);
+      out << " = ";
+      ExpressionToCarbon(alias.target(), out);
+      out << ";";
+      break;
+    }
   }
 }
 

+ 1 - 0
explorer/ast/ast_rtti.txt

@@ -19,6 +19,7 @@ abstract class Declaration : AstNode;
   class VariableDeclaration : Declaration;
   class InterfaceDeclaration : Declaration;
   class ImplDeclaration : Declaration;
+  class AliasDeclaration : Declaration;
 class ImplBinding : AstNode;
 class AlternativeSignature : AstNode;
 abstract class Statement : AstNode;

+ 16 - 0
explorer/ast/declaration.cpp

@@ -78,6 +78,13 @@ void Declaration::Print(llvm::raw_ostream& out) const {
       out << "Self";
       break;
     }
+
+    case DeclarationKind::AliasDeclaration: {
+      const auto& alias = cast<AliasDeclaration>(*this);
+      PrintID(out);
+      out << " = " << alias.target() << ";\n";
+      break;
+    }
   }
 }
 
@@ -127,6 +134,12 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
       out << "Self";
       break;
     }
+
+    case DeclarationKind::AliasDeclaration: {
+      const auto& alias = cast<AliasDeclaration>(*this);
+      out << "alias " << alias.name();
+      break;
+    }
   }
 }
 
@@ -146,6 +159,9 @@ auto GetName(const Declaration& declaration) -> std::optional<std::string> {
       return std::nullopt;
     case DeclarationKind::SelfDeclaration:
       return cast<SelfDeclaration>(declaration).name();
+    case DeclarationKind::AliasDeclaration: {
+      return cast<AliasDeclaration>(declaration).name();
+    }
   }
 }
 

+ 24 - 0
explorer/ast/declaration.h

@@ -417,6 +417,30 @@ class ImplDeclaration : public Declaration {
   std::vector<Nonnull<const ImplBinding*>> impl_bindings_;
 };
 
+class AliasDeclaration : public Declaration {
+ public:
+  using ImplementsCarbonValueNode = void;
+
+  explicit AliasDeclaration(SourceLocation source_loc, const std::string& name,
+                            Nonnull<Expression*> target)
+      : Declaration(AstNodeKind::AliasDeclaration, source_loc),
+        name_(name),
+        target_(target) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromAliasDeclaration(node->kind());
+  }
+
+  auto name() const -> const std::string { return name_; }
+  auto target() const -> const Expression& { return *target_; }
+  auto target() -> Expression& { return *target_; }
+  auto value_category() const -> ValueCategory { return ValueCategory::Let; }
+
+ private:
+  std::string name_;
+  Nonnull<Expression*> target_;
+};
+
 // Return the name of a declaration, if it has one.
 auto GetName(const Declaration&) -> std::optional<std::string>;
 

+ 8 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -563,6 +563,14 @@ static auto DeclarationToProto(const Declaration& declaration)
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable SelfDeclaration in DeclarationToProto().";
     }
+
+    case DeclarationKind::AliasDeclaration: {
+      const auto& alias = cast<AliasDeclaration>(declaration);
+      auto* alias_proto = declaration_proto.mutable_alias();
+      alias_proto->set_name(alias.name());
+      *alias_proto->mutable_target() = ExpressionToProto(alias.target());
+      break;
+    }
   }
   return declaration_proto;
 }

+ 1 - 0
explorer/interpreter/interpreter.cpp

@@ -1400,6 +1400,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
     case DeclarationKind::InterfaceDeclaration:
     case DeclarationKind::ImplDeclaration:
     case DeclarationKind::SelfDeclaration:
+    case DeclarationKind::AliasDeclaration:
       // These declarations have no run-time effects.
       return todo_.FinishAction();
   }

+ 1 - 0
explorer/interpreter/resolve_control_flow.cpp

@@ -154,6 +154,7 @@ auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
     case DeclarationKind::ChoiceDeclaration:
     case DeclarationKind::VariableDeclaration:
     case DeclarationKind::SelfDeclaration:
+    case DeclarationKind::AliasDeclaration:
       // do nothing
       break;
   }

+ 11 - 0
explorer/interpreter/resolve_names.cpp

@@ -64,6 +64,11 @@ static auto AddExposedNames(const Declaration& declaration,
       CARBON_RETURN_IF_ERROR(enclosing_scope.Add("Self", &self));
       break;
     }
+    case DeclarationKind::AliasDeclaration: {
+      auto& alias = cast<AliasDeclaration>(declaration);
+      CARBON_RETURN_IF_ERROR(enclosing_scope.Add(alias.name(), &alias));
+      break;
+    }
   }
   return Success();
 }
@@ -446,6 +451,12 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope)
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable: resolving names for `Self` declaration";
     }
+
+    case DeclarationKind::AliasDeclaration: {
+      CARBON_RETURN_IF_ERROR(ResolveNames(
+          cast<AliasDeclaration>(declaration).target(), enclosing_scope));
+      break;
+    }
   }
   return Success();
 }

+ 75 - 0
explorer/interpreter/type_checker.cpp

@@ -2409,6 +2409,72 @@ auto TypeChecker::TypeCheckChoiceDeclaration(
   return Success();
 }
 
+static bool IsValidTypeForAliasTarget(Nonnull<const Value*> type) {
+  switch (type->kind()) {
+    case Value::Kind::IntValue:
+    case Value::Kind::FunctionValue:
+    case Value::Kind::BoundMethodValue:
+    case Value::Kind::PointerValue:
+    case Value::Kind::LValue:
+    case Value::Kind::BoolValue:
+    case Value::Kind::StructValue:
+    case Value::Kind::NominalClassValue:
+    case Value::Kind::AlternativeValue:
+    case Value::Kind::TupleValue:
+    case Value::Kind::Witness:
+    case Value::Kind::ParameterizedEntityName:
+    case Value::Kind::MemberName:
+    case Value::Kind::BindingPlaceholderValue:
+    case Value::Kind::AlternativeConstructorValue:
+    case Value::Kind::ContinuationValue:
+    case Value::Kind::StringValue:
+      CARBON_FATAL() << "type of alias target is not a type";
+
+    case Value::Kind::AutoType:
+    case Value::Kind::VariableType:
+      CARBON_FATAL() << "pattern type in alias target";
+
+    case Value::Kind::IntType:
+    case Value::Kind::BoolType:
+    case Value::Kind::PointerType:
+    case Value::Kind::StaticArrayType:
+    case Value::Kind::StructType:
+    case Value::Kind::NominalClassType:
+    case Value::Kind::ChoiceType:
+    case Value::Kind::ContinuationType:
+    case Value::Kind::StringType:
+      return false;
+
+    case Value::Kind::FunctionType:
+    case Value::Kind::InterfaceType:
+    case Value::Kind::TypeType:
+    case Value::Kind::TypeOfClassType:
+    case Value::Kind::TypeOfInterfaceType:
+    case Value::Kind::TypeOfChoiceType:
+    case Value::Kind::TypeOfParameterizedEntityName:
+    case Value::Kind::TypeOfMemberName:
+      return true;
+  }
+}
+
+auto TypeChecker::DeclareAliasDeclaration(Nonnull<AliasDeclaration*> alias,
+                                          const ImplScope& enclosing_scope)
+    -> ErrorOr<Success> {
+  CARBON_RETURN_IF_ERROR(TypeCheckExp(&alias->target(), enclosing_scope));
+
+  if (!IsValidTypeForAliasTarget(&alias->target().static_type())) {
+    return CompilationError(alias->source_loc())
+           << "invalid target for alias declaration";
+  }
+
+  CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> target,
+                          InterpExp(&alias->target(), arena_, trace_stream_));
+
+  SetConstantValue(alias, target);
+  alias->set_static_type(&alias->target().static_type());
+  return Success();
+}
+
 auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr<Success> {
   ImplScope impl_scope;
   for (Nonnull<Declaration*> declaration : ast.declarations) {
@@ -2472,6 +2538,9 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull<Declaration*> d,
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable TypeChecker `Self` declaration";
     }
+    case DeclarationKind::AliasDeclaration: {
+      return Success();
+    }
   }
   return Success();
 }
@@ -2534,6 +2603,12 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
     case DeclarationKind::SelfDeclaration: {
       CARBON_FATAL() << "Unreachable TypeChecker declare `Self` declaration";
     }
+
+    case DeclarationKind::AliasDeclaration: {
+      auto& alias = cast<AliasDeclaration>(*d);
+      CARBON_RETURN_IF_ERROR(DeclareAliasDeclaration(&alias, enclosing_scope));
+      break;
+    }
   }
   return Success();
 }

+ 3 - 0
explorer/interpreter/type_checker.h

@@ -106,6 +106,9 @@ class TypeChecker {
   auto DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
                                 const ImplScope& enclosing_scope)
       -> ErrorOr<Success>;
+  auto DeclareAliasDeclaration(Nonnull<AliasDeclaration*> alias,
+                               const ImplScope& enclosing_scope)
+      -> ErrorOr<Success>;
 
   // Find all of the GenericBindings in the given pattern.
   void CollectGenericBindingsInPattern(

+ 2 - 0
explorer/syntax/lexer.lpp

@@ -33,6 +33,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 %s AFTER_OPERAND
 
 /* table-begin */
+ALIAS                "alias"
 AMPERSAND            "&"
 AND                  "and"
 API                  "api"
@@ -137,6 +138,7 @@ string_literal        \"([^\\\"\n\v\f\r]|\\.)*\"
 %}
 
  /* table-begin */
+{ALIAS}               { return SIMPLE_TOKEN(ALIAS);               }
 {AMPERSAND}           { return SIMPLE_TOKEN(AMPERSAND);           }
 {AND}                 { return SIMPLE_TOKEN(AND);                 }
 {API}                 { return SIMPLE_TOKEN(API);                 }

+ 7 - 0
explorer/syntax/parser.ypp

@@ -106,6 +106,7 @@
 %type <bool> api_or_impl
 %type <Nonnull<Declaration*>> declaration
 %type <Nonnull<FunctionDeclaration*>> function_declaration
+%type <Nonnull<AliasDeclaration*>> alias_declaration
 %type <std::vector<Nonnull<Declaration*>>> declaration_list
 %type <Nonnull<Statement*>> statement
 %type <Nonnull<If*>> if_statement
@@ -173,6 +174,7 @@
 %token
   // Most tokens have their spelling defined in lexer.lpp.
   // table-begin
+  ALIAS
   AMPERSAND
   AND
   API
@@ -864,6 +866,9 @@ variable_declaration: identifier COLON pattern
                                       std::nullopt);
     }
 ;
+alias_declaration: ALIAS identifier EQUAL expression SEMICOLON
+    { $$ = arena->New<AliasDeclaration>(context.source_loc(), $2, $4); }
+;
 alternative:
   identifier tuple
     { $$ = arena->New<AlternativeSignature>(context.source_loc(), $1, $2); }
@@ -945,6 +950,8 @@ declaration:
         YYERROR;
       }
     }
+| alias_declaration
+    { $$ = $1; }
 ;
 impl_kind:
   // Internal

+ 40 - 0
explorer/testdata/alias/class_alias.carbon

@@ -0,0 +1,40 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 123
+
+package ExplorerTest api;
+
+interface Addable {
+  fn Add[me: Self](k: i32) -> Self;
+}
+impl i32 as Addable {
+  fn Add[me: Self](k: i32) -> Self { return me + k; }
+}
+
+class Class { var n: i32; }
+class GenericClass(T:! Addable) {
+  var m: T;
+  fn Get[me: Self](n: i32) -> T { return me.m.Add(n); }
+}
+
+alias ClassAlias = Class;
+alias GenericClassAlias = GenericClass;
+alias ClassSpecializationAlias = GenericClassAlias(i32);
+
+fn Main() -> i32 {
+  var a: Class = {.n = 1};
+  var b: ClassAlias = a;
+
+  var c: GenericClass(i32) = {.m = 2};
+  var d: GenericClassAlias(i32) = c;
+  var e: ClassSpecializationAlias = c;
+  // TODO: Switch to using Print here once it supports printing integers.
+  return 100 * b.n + 10 * d.Get(0) + e.Get(1);
+}

+ 16 - 0
explorer/testdata/alias/fail_alias_expression.carbon

@@ -0,0 +1,16 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/alias/fail_alias_expression.carbon:[[@LINE+1]]: invalid target for alias declaration
+alias A = 5;
+
+fn Main() -> i32 { return 0; }

+ 18 - 0
explorer/testdata/alias/fail_alias_var.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+var v: i32;
+// FIXME: This should probably be valid if we allow global variables at all.
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/alias/fail_alias_var.carbon:[[@LINE+1]]: invalid target for alias declaration
+alias A = v;
+
+fn Main() -> i32 { return 0; }

+ 24 - 0
explorer/testdata/alias/function_alias.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 7
+
+package ExplorerTest api;
+
+alias TypeAlias = i32;
+
+fn Function(a: i32, b: TypeAlias) -> i32 { return a + b; }
+fn GenericFunction[T:! Type](x: T) -> T { return x; }
+
+alias FunctionAlias = Function;
+alias GenericFunctionAlias = GenericFunction;
+
+fn Main() -> i32 {
+  return FunctionAlias(1, 2) + GenericFunctionAlias(4);
+}

+ 62 - 0
explorer/testdata/alias/interface_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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 12345
+
+package ExplorerTest api;
+
+class Class { var a: i32; }
+class GenericClass(T:! Type) { var a: T; }
+interface Interface { fn Make() -> Self; }
+interface GenericInterface(T:! Type) { fn Make() -> (Self, T); }
+
+alias ClassAlias = Class;
+alias GenericClassAlias = GenericClass;
+alias ClassSpecializationAlias = GenericClassAlias(i32);
+alias InterfaceAlias = Interface;
+alias GenericInterfaceAlias = GenericInterface;
+alias InterfaceSpecializationAlias = GenericInterfaceAlias(i32);
+
+impl ClassAlias as InterfaceAlias {
+  fn Make() -> Self { return {.a = 1}; }
+}
+impl ClassSpecializationAlias as InterfaceSpecializationAlias {
+  fn Make() -> (Self, i32) { return ({.a = 2}, 3); }
+}
+alias S = {.a: i32};
+impl GenericClassAlias(S) as GenericInterfaceAlias(S) {
+  fn Make() -> (Self, {.a: i32}) { return ({.a = {.a = 4}}, {.a = 5}); }
+}
+
+fn CheckImplementsInterface[T:! Interface](x: T) -> T {
+  return T.Make();
+}
+fn CheckImplementsGenericInterface_i32
+    [T:! GenericInterface(i32)](x: T) -> (T, i32) {
+  return T.Make();
+}
+fn CheckImplementsGenericInterface_struct
+    [T:! GenericInterface(S)](x: T) -> (T, S) {
+  return T.Make();
+}
+
+fn Main() -> i32 {
+  var a: Class = {.a = 0};
+  a = CheckImplementsInterface(a);
+
+  var b: GenericClass(i32) = {.a = 0};
+  var (b2: GenericClass(i32), n: i32) =
+    CheckImplementsGenericInterface_i32(b);
+
+  var c: GenericClass({.a: i32}) = {.a = {.a = 0}};
+  var (c2: GenericClass({.a: i32}), s: S) =
+    CheckImplementsGenericInterface_struct(c);
+
+  return 10000 * a.a + 1000 * b2.a + 100 * n + 10 * c2.a.a + s.a;
+}

+ 82 - 0
explorer/testdata/alias/member_name_alias.carbon

@@ -0,0 +1,82 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 0
+
+package ExplorerTest api;
+
+interface I {
+  fn F() -> i32;
+  fn M[me: Self]() -> i32;
+}
+class A {
+  var n: i32;
+  impl as I {
+    fn F() -> i32 { return 1; }
+    fn M[me: Self]() -> i32 { return 2; }
+  }
+  fn G[me: Self]() -> i32 { return 3; }
+}
+impl i32 as I {
+  fn F() -> i32 { return 4; }
+  fn M[me: Self]() -> i32 { return 5; }
+}
+
+alias IF = I.F;
+alias IM = I.M;
+alias AIF = A.(IF);
+alias AIM = A.(IM);
+alias AG = A.G;
+alias i32IF = i32.(IF);
+alias i32IM = i32.(IM);
+
+fn Main() -> i32 {
+  var a: A = {.n = 0};
+  // FIXME: Use != once we support it.
+  if (not (A.(IF)() == 1)) {
+    return 1;
+  }
+  if (not (a.(IF)() == 1)) {
+    return 2;
+  }
+  if (not (a.(IM)() == 2)) {
+    return 3;
+  }
+  if (not (a.(A.(IM))() == 2)) {
+    return 4;
+  }
+  if (not (AIF() == 1)) {
+    return 5;
+  }
+  if (not (a.(AIM)() == 2)) {
+    return 6;
+  }
+  if (not (a.(AG)() == 3)) {
+    return 7;
+  }
+  if (not (i32.(IF)() == 4)) {
+    return 8;
+  }
+  if (not (0.(IF)() == 4)) {
+    return 9;
+  }
+  if (not (0.(IM)() == 5)) {
+    return 10;
+  }
+  if (not (0.(i32.(IM))() == 5)) {
+    return 11;
+  }
+  if (not (i32IF() == 4)) {
+    return 12;
+  }
+  if (not (0.(i32IM)() == 5)) {
+    return 13;
+  }
+  return 0;
+}

+ 22 - 0
explorer/testdata/alias/struct_alias.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 2121
+
+package ExplorerTest api;
+
+alias AB = {.a: i32, .b: i32};
+alias BA = {.b: i32, .a: i32};
+
+fn Main() -> i32 {
+  var ab: AB = {.b = 1, .a = 2};
+  var ba: BA = ab;
+  // TODO: Switch to using Print here once it supports printing integers.
+  return 1000 * ab.a + 100 * ab.b + 10 * ba.a + ba.b;
+}

+ 22 - 0
explorer/testdata/alias/type_alias.carbon

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 4
+
+package ExplorerTest api;
+
+alias TypeAlias = i32;
+
+fn Main() -> TypeAlias {
+  var n: TypeAlias = 1;
+  var m: i32 = n;
+  var p: i32* = &n;
+  var q: TypeAlias* = &m;
+  return *p + *q + m + n;
+}