[dart2js] Implement encoder for recipes

Change-Id: Ia8fd313b014730859796abaf0083fdf738034365
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106941
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart
index 5e22b23..b367d3a 100644
--- a/pkg/compiler/lib/src/js_backend/backend.dart
+++ b/pkg/compiler/lib/src/js_backend/backend.dart
@@ -20,6 +20,7 @@
 import 'checked_mode_helpers.dart';
 import 'namer.dart';
 import 'runtime_types.dart';
+import 'runtime_types_new.dart';
 
 abstract class FunctionCompiler {
   void initialize(
@@ -305,6 +306,7 @@
   CheckedModeHelpers get checkedModeHelpers;
   RuntimeTypesSubstitutions get rtiSubstitutions;
   RuntimeTypesEncoder get rtiEncoder;
+  RecipeEncoder get rtiRecipeEncoder;
   Tracer get tracer;
   RuntimeTypeTags get rtiTags;
   FixedNames get fixedNames;
@@ -321,6 +323,9 @@
   final RuntimeTypesEncoder rtiEncoder;
 
   @override
+  final RecipeEncoder rtiRecipeEncoder;
+
+  @override
   final Tracer tracer;
 
   @override
@@ -329,6 +334,6 @@
   @override
   final FixedNames fixedNames;
 
-  CodegenInputsImpl(this.rtiSubstitutions, this.rtiEncoder, this.tracer,
-      this.rtiTags, this.fixedNames);
+  CodegenInputsImpl(this.rtiSubstitutions, this.rtiEncoder,
+      this.rtiRecipeEncoder, this.tracer, this.rtiTags, this.fixedNames);
 }
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index 9f683b7..d80aa0a 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -101,7 +101,7 @@
       _commonElements.getRuntimeTypeArgumentIntercepted,
       _commonElements.getRuntimeTypeArgument,
       _commonElements.getTypeArgumentByIndex,
-    ]);
+    ], otherImpacts: newRtiImpacts('getRuntimeTypeArgument'));
   }
 
   BackendImpact _computeSignature;
@@ -189,7 +189,7 @@
   BackendImpact get asCheck {
     return _asCheck ??= new BackendImpact(staticUses: [
       _commonElements.throwRuntimeError,
-    ], otherImpacts: _newRti ? [usesNewRti] : []);
+    ], otherImpacts: newRtiImpacts('asCheck'));
   }
 
   BackendImpact _throwNoSuchMethod;
@@ -762,15 +762,16 @@
         _commonElements.getInstantiationClass(typeArgumentCount),
       ]);
 
-  BackendImpact _usesNewRti;
-
   /// Backend impact for --experiment-new-rti.
-  BackendImpact get usesNewRti {
-    // TODO(sra): Can this be broken down into more selective impacts?
-    return _usesNewRti ??= BackendImpact(staticUses: [
-      _commonElements.findType,
-      _commonElements.rtiEvalMethod,
-      _commonElements.rtiBindMethod,
-    ]);
+  List<BackendImpact> newRtiImpacts(String what) {
+    if (!_newRti) return [];
+    // TODO(sra): Split into refined impacts.
+    return [
+      BackendImpact(staticUses: [
+        _commonElements.findType,
+        _commonElements.rtiEvalMethod,
+        _commonElements.rtiBindMethod,
+      ])
+    ];
   }
 }
diff --git a/pkg/compiler/lib/src/js_backend/namer_names.dart b/pkg/compiler/lib/src/js_backend/namer_names.dart
index f405dc0..61d721a 100644
--- a/pkg/compiler/lib/src/js_backend/namer_names.dart
+++ b/pkg/compiler/lib/src/js_backend/namer_names.dart
@@ -140,6 +140,14 @@
 
   CompoundName(this._parts);
 
+  // TODO(37302): Use
+  // CompoundName.from(List<jsAst.Name> parts) : this(<_NamerName>[...parts]);
+  CompoundName.from(List<jsAst.Name> parts) : _parts = [] {
+    for (var part in parts) {
+      _parts.add(part);
+    }
+  }
+
   @override
   String get name {
     if (_cachedName == null) {
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types_new.dart b/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
new file mode 100644
index 0000000..b75b178
--- /dev/null
+++ b/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
@@ -0,0 +1,384 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library js_backend.runtime_types_new;
+
+import 'package:js_runtime/shared/recipe_syntax.dart';
+
+import '../common_elements.dart' show JCommonElements, JElementEnvironment;
+import '../elements/entities.dart';
+import '../elements/types.dart';
+import '../js/js.dart' as jsAst;
+import '../js/js.dart' show js;
+import '../js_model/type_recipe.dart';
+import '../js_emitter/js_emitter.dart' show ModularEmitter;
+import '../universe/class_hierarchy.dart';
+import '../world.dart';
+import 'namer.dart';
+import 'native_data.dart';
+
+import 'runtime_types.dart' show RuntimeTypesNeed, RuntimeTypesSubstitutions;
+
+abstract class RecipeEncoder {
+  /// Returns a [jsAst.Expression] representing the given [recipe] to be
+  /// evaluated against a type environment with shape [structure].
+  jsAst.Expression encodeRecipe(ModularEmitter emitter,
+      TypeEnvironmentStructure environmentStructure, TypeRecipe recipe);
+
+  jsAst.Expression encodeGroundRecipe(
+      ModularEmitter emitter, TypeRecipe recipe);
+
+  // TODO(sra): Still need a $signature function when the function type is a
+  // function of closed type variables. See if the $signature method can always
+  // be generated through SSA in those cases.
+  jsAst.Expression encodeSignature(ModularNamer namer, ModularEmitter emitter,
+      DartType type, jsAst.Expression this_);
+}
+
+class RecipeEncoderImpl implements RecipeEncoder {
+  final JClosedWorld _closedWorld;
+  final RuntimeTypesSubstitutions _rtiSubstitutions;
+  final NativeBasicData _nativeData;
+  final JElementEnvironment _elementEnvironment;
+  final JCommonElements commonElements;
+  final RuntimeTypesNeed _rtiNeed;
+
+  RecipeEncoderImpl(this._closedWorld, this._rtiSubstitutions, this._nativeData,
+      this._elementEnvironment, this.commonElements, this._rtiNeed);
+
+  @override
+  jsAst.Expression encodeRecipe(ModularEmitter emitter,
+      TypeEnvironmentStructure environmentStructure, TypeRecipe recipe) {
+    return _RecipeGenerator(this, emitter, environmentStructure, recipe).run();
+  }
+
+  @override
+  jsAst.Expression encodeGroundRecipe(
+      ModularEmitter emitter, TypeRecipe recipe) {
+    return _RecipeGenerator(this, emitter, null, recipe).run();
+  }
+
+  @override
+  jsAst.Expression encodeSignature(ModularNamer namer, ModularEmitter emitter,
+      DartType type, jsAst.Expression this_) {
+    // TODO(sra): These inputs (referenced to quell lints) are used by the old
+    // rti signature generator. Do we need them?
+    _rtiNeed;
+    commonElements;
+    _elementEnvironment;
+    throw UnimplementedError('RecipeEncoderImpl.getSignatureEncoding');
+  }
+}
+
+class _RecipeGenerator implements DartTypeVisitor<void, void> {
+  final RecipeEncoderImpl _encoder;
+  final ModularEmitter _emitter;
+  final TypeEnvironmentStructure _environment;
+  final TypeRecipe _recipe;
+
+  final List<FunctionTypeVariable> functionTypeVariables = [];
+
+  // Accumulated recipe.
+  final List<jsAst.Literal> _fragments = [];
+  final List<int> _codes = [];
+
+  _RecipeGenerator(
+      this._encoder, this._emitter, this._environment, this._recipe);
+
+  JClosedWorld get _closedWorld => _encoder._closedWorld;
+  NativeBasicData get _nativeData => _encoder._nativeData;
+  RuntimeTypesSubstitutions get _rtiSubstitutions => _encoder._rtiSubstitutions;
+
+  jsAst.Expression run() {
+    _start(_recipe);
+    assert(functionTypeVariables.isEmpty);
+    if (_fragments.isEmpty) {
+      return js.string(String.fromCharCodes(_codes));
+    }
+    _flushCodes();
+    jsAst.LiteralString quote = jsAst.LiteralString('"');
+    // TODO(37302): Use [quote, ..._fragments, quote].
+    return jsAst.StringConcatenation(<jsAst.Literal>[quote]
+      ..addAll(_fragments)
+      ..add(quote));
+  }
+
+  void _start(TypeRecipe recipe) {
+    if (recipe is TypeExpressionRecipe) {
+      visit(recipe.type, null);
+    } else if (recipe is SingletonTypeEnvironmentRecipe) {
+      visit(recipe.type, null);
+    } else if (recipe is FullTypeEnvironmentRecipe) {
+      _startFullTypeEnvironmentRecipe(recipe, null);
+    }
+  }
+
+  void _startFullTypeEnvironmentRecipe(FullTypeEnvironmentRecipe recipe, _) {
+    if (recipe.classType == null) {
+      _emitCode(Recipe.pushDynamic);
+      assert(recipe.types.isNotEmpty);
+    } else {
+      visit(recipe.classType, null);
+      // TODO(sra): The separator can be omitted when the parser will have
+      // reduced to the top of stack to an Rti value.
+      _emitCode(Recipe.toType);
+    }
+
+    if (recipe.types.isNotEmpty) {
+      _emitCode(Recipe.startTypeArguments);
+      bool first = true;
+      for (DartType type in recipe.types) {
+        if (!first) {
+          _emitCode(Recipe.separator);
+        }
+        visit(type, _);
+        first = false;
+      }
+      _emitCode(Recipe.endTypeArguments);
+    }
+  }
+
+  void _emitCode(int code) {
+    // TODO(sra): We should permit codes with short escapes (like '\n') for
+    // infrequent operators.
+    assert(code >= 0x20 && code <= 0x7E && code != 0x22);
+    _codes.add(code);
+  }
+
+  void _flushCodes() {
+    if (_codes.isEmpty) return;
+    // TODO(sra): codes need some escaping.
+    _fragments.add(StringBackedName(String.fromCharCodes(_codes)));
+    _codes.clear();
+  }
+
+  void _emitInteger(int value) {
+    if (_codes.isEmpty ? _fragments.isNotEmpty : Recipe.isDigit(_codes.last)) {
+      _emitCode(Recipe.separator);
+    }
+    _emitStringUnescaped('$value');
+  }
+
+  void _emitStringUnescaped(String string) {
+    for (int code in string.codeUnits) {
+      _emitCode(code);
+    }
+  }
+
+  void _emitName(jsAst.Name name) {
+    if (_fragments.isNotEmpty && _codes.isEmpty) {
+      _emitCode(Recipe.separator);
+    }
+    _flushCodes();
+    _fragments.add(name);
+  }
+
+  void _emitExtensionOp(int value) {
+    _emitInteger(value);
+    _emitCode(Recipe.extensionOp);
+  }
+
+  @override
+  void visit(DartType type, _) => type.accept(this, _);
+
+  @override
+  void visitTypeVariableType(TypeVariableType type, _) {
+    TypeEnvironmentStructure environment = _environment;
+    if (environment is SingletonTypeEnvironmentStructure) {
+      if (type == environment.variable) {
+        _emitInteger(0);
+        return;
+      }
+    }
+    if (environment is FullTypeEnvironmentStructure) {
+      int i = environment.bindings.indexOf(type);
+      if (i >= 0) {
+        // Indexes are 1-based since '0' encodes using the entire type for the
+        // singleton structure.
+        _emitInteger(i + 1);
+        return;
+      }
+
+      int index = _indexIntoClassTypeVariables(type);
+      if (index != null) {
+        // Indexed class type variables come after the bound function type
+        // variables.
+        _emitInteger(1 + environment.bindings.length + index);
+        return;
+      }
+      // TODO(sra): Encode type variables names via namer.
+    }
+    // TODO(sra): Handle missing cases. This just emits some readable junk. The
+    // backticks ensure it won't parse at runtime.
+    '`$type`'.codeUnits.forEach(_emitCode);
+  }
+
+  int /*?*/ _indexIntoClassTypeVariables(TypeVariableType variable) {
+    TypeVariableEntity element = variable.element;
+    ClassEntity cls = element.typeDeclaration;
+
+    // TODO(sra): We might be in a context where the class type variable has an
+    // index, even though in the general case it is not at a specific index.
+
+    if (_closedWorld.isUsedAsMixin(cls)) return null;
+
+    ClassHierarchy classHierarchy = _closedWorld.classHierarchy;
+    if (classHierarchy.anyStrictSubclassOf(cls, (ClassEntity subclass) {
+      return !_rtiSubstitutions.isTrivialSubstitution(subclass, cls);
+    })) {
+      return null;
+    }
+    return element.index;
+  }
+
+  @override
+  void visitFunctionTypeVariable(FunctionTypeVariable type, _) {
+    int position = functionTypeVariables.indexOf(type);
+    assert(position >= 0);
+    // See [visitFunctionType] for explanation.
+    _emitInteger(functionTypeVariables.length - position - 1);
+    _emitCode(Recipe.genericFunctionTypeParameterIndex);
+  }
+
+  @override
+  void visitDynamicType(DynamicType type, _) {
+    _emitCode(Recipe.pushDynamic);
+  }
+
+  @override
+  void visitInterfaceType(InterfaceType type, _) {
+    jsAst.Name name = _emitter.typeAccessNewRti(type.element);
+    if (type.typeArguments.isEmpty) {
+      // Push the name, which is later converted by an implicit toType
+      // operation.
+      _emitName(name);
+    } else {
+      _emitName(name);
+      _emitCode(Recipe.startTypeArguments);
+      bool first = true;
+      for (DartType argumentType in type.typeArguments) {
+        if (!first) {
+          _emitCode(Recipe.separator);
+        }
+        if (_nativeData.isJsInteropClass(type.element)) {
+          // Emit 'any' type.
+          _emitExtensionOp(Recipe.pushAnyExtension);
+        } else {
+          visit(argumentType, _);
+        }
+        first = false;
+      }
+      _emitCode(Recipe.endTypeArguments);
+    }
+  }
+
+  @override
+  void visitFunctionType(FunctionType type, _) {
+    if (type.typeVariables.isNotEmpty) {
+      // Enter generic function scope.
+      //
+      // Function type variables are encoded as a modified de Bruin index. We
+      // count variables from the current scope outwards, counting the variables
+      // in the same scope left-to-right.
+      //
+      // If we push the current scope's variables in reverse, then the index is
+      // the position measured from the end.
+      //
+      //    foo<AA,BB>() => ...
+      //      //^0 ^1
+      //    functionTypeVariables: [BB,AA]
+      //
+      //    foo<AA,BB>() => <UU,VV,WW>() => ...
+      //        ^3 ^4        ^0 ^1 ^2
+      //    functionTypeVariables: [BB,AA,WW,VV,UU]
+      //
+      for (FunctionTypeVariable variable in type.typeVariables.reversed) {
+        functionTypeVariables.add(variable);
+      }
+    }
+
+    visit(type.returnType, _);
+    _emitCode(Recipe.startFunctionArguments);
+
+    bool first = true;
+    for (DartType parameterType in type.parameterTypes) {
+      if (!first) {
+        _emitCode(Recipe.separator);
+      }
+      visit(parameterType, _);
+      first = false;
+    }
+
+    if (type.optionalParameterTypes.isNotEmpty) {
+      first = true;
+      _emitCode(Recipe.startOptionalGroup);
+      for (DartType parameterType in type.optionalParameterTypes) {
+        if (!first) {
+          _emitCode(Recipe.separator);
+        }
+        visit(parameterType, _);
+        first = false;
+      }
+      _emitCode(Recipe.endOptionalGroup);
+    }
+
+    void emitNamedGroup(List<String> names, List<DartType> types) {
+      assert(names.length == types.length);
+      first = true;
+      _emitCode(Recipe.startNamedGroup);
+      for (int i = 0; i < names.length; i++) {
+        if (!first) {
+          _emitCode(Recipe.separator);
+        }
+        _emitStringUnescaped(names[i]);
+        _emitCode(Recipe.nameSeparator);
+        visit(types[i], _);
+        first = false;
+      }
+      _emitCode(Recipe.endNamedGroup);
+    }
+
+    // TODO(sra): These are optional named parameters. Handle required named
+    // parameters the same way when they are implemented.
+    if (type.namedParameterTypes.isNotEmpty) {
+      emitNamedGroup(type.namedParameters, type.namedParameterTypes);
+    }
+
+    _emitCode(Recipe.endFunctionArguments);
+
+    // Emit generic type bounds.
+    if (type.typeVariables.isNotEmpty) {
+      bool first = true;
+      _emitCode(Recipe.startTypeArguments);
+      for (FunctionTypeVariable typeVariable in type.typeVariables) {
+        if (!first) {
+          _emitCode(Recipe.separator);
+        }
+        visit(typeVariable.bound, _);
+      }
+      _emitCode(Recipe.endTypeArguments);
+    }
+
+    if (type.typeVariables.isNotEmpty) {
+      // Exit generic function scope. Remove the type variables pushed at entry.
+      functionTypeVariables.length -= type.typeVariables.length;
+    }
+  }
+
+  @override
+  void visitVoidType(VoidType type, _) {
+    _emitCode(Recipe.pushVoid);
+  }
+
+  @override
+  void visitTypedefType(TypedefType type, _) {
+    visit(type.unaliased, _);
+  }
+
+  @override
+  void visitFutureOrType(FutureOrType type, _) {
+    visit(type.typeArgument, _);
+    _emitCode(Recipe.wrapFutureOr);
+  }
+}
diff --git a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
index c9b0ec1..5d3408b 100644
--- a/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
+++ b/pkg/compiler/lib/src/js_emitter/code_emitter_task.dart
@@ -179,6 +179,9 @@
   /// Returns the JS expression representing the type [e].
   jsAst.Expression typeAccess(Entity e);
 
+  /// Returns the JS name representing the type [e].
+  jsAst.Name typeAccessNewRti(Entity e);
+
   /// Returns the JS code for accessing the embedded [global].
   jsAst.Expression generateEmbeddedGlobalAccess(String global);
 
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
index c664a0f..d5ad70d 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/emitter.dart
@@ -86,6 +86,11 @@
   }
 
   @override
+  js.Name typeAccessNewRti(Entity element) {
+    return _namer.globalPropertyNameForType(element);
+  }
+
+  @override
   js.Expression staticClosureAccess(FunctionEntity element) {
     return new js.Call(
         new js.PropertyAccess(_namer.readGlobalObjectForMember(element),
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index 2c0c686..f794857 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -34,6 +34,8 @@
 import '../js_backend/interceptor_data.dart';
 import '../js_backend/namer.dart';
 import '../js_backend/runtime_types.dart';
+import '../js_backend/runtime_types_new.dart'
+    show RecipeEncoder, RecipeEncoderImpl;
 import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
 import '../js_emitter/js_emitter.dart' show CodeEmitterTask;
 import '../js/js.dart' as js;
@@ -202,6 +204,7 @@
         closedWorld.elementEnvironment,
         closedWorld.commonElements,
         closedWorld.rtiNeed);
+
     RuntimeTypesSubstitutions rtiSubstitutions;
     if (_compiler.options.disableRtiOptimization) {
       rtiSubstitutions = new TrivialRuntimeTypesSubstitutions(closedWorld);
@@ -213,8 +216,18 @@
       rtiSubstitutions = runtimeTypesImpl;
     }
 
-    CodegenInputs codegen = new CodegenInputsImpl(
-        rtiSubstitutions, rtiEncoder, tracer, rtiTags, fixedNames);
+    RecipeEncoder rtiRecipeEncoder = _compiler.options.experimentNewRti
+        ? new RecipeEncoderImpl(
+            closedWorld,
+            rtiSubstitutions,
+            closedWorld.nativeData,
+            closedWorld.elementEnvironment,
+            closedWorld.commonElements,
+            closedWorld.rtiNeed)
+        : null;
+
+    CodegenInputs codegen = new CodegenInputsImpl(rtiSubstitutions, rtiEncoder,
+        rtiRecipeEncoder, tracer, rtiTags, fixedNames);
 
     functionCompiler.initialize(globalTypeInferenceResults, codegen);
     return codegen;
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index a80a7a6..6e81b1c 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -26,8 +26,10 @@
 import '../js_backend/native_data.dart';
 import '../js_backend/namer.dart' show ModularNamer;
 import '../js_backend/runtime_types.dart';
+import '../js_backend/runtime_types_new.dart' show RecipeEncoder;
 import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
 import '../js_model/elements.dart' show JGeneratorBody;
+import '../js_model/type_recipe.dart' show TypeExpressionRecipe;
 import '../native/behavior.dart';
 import '../options.dart';
 import '../tracer.dart';
@@ -114,6 +116,7 @@
           codegen.checkedModeHelpers,
           codegen.rtiSubstitutions,
           codegen.rtiEncoder,
+          codegen.rtiRecipeEncoder,
           namer,
           codegen.tracer,
           closedWorld,
@@ -143,6 +146,7 @@
           codegen.checkedModeHelpers,
           codegen.rtiSubstitutions,
           codegen.rtiEncoder,
+          codegen.rtiRecipeEncoder,
           namer,
           codegen.tracer,
           closedWorld,
@@ -180,6 +184,7 @@
   final CheckedModeHelpers _checkedModeHelpers;
   final RuntimeTypesSubstitutions _rtiSubstitutions;
   final RuntimeTypesEncoder _rtiEncoder;
+  final RecipeEncoder _rtiRecipeEncoder;
   final ModularNamer _namer;
   final Tracer _tracer;
   final JClosedWorld _closedWorld;
@@ -234,6 +239,7 @@
       this._checkedModeHelpers,
       this._rtiSubstitutions,
       this._rtiEncoder,
+      this._rtiRecipeEncoder,
       this._namer,
       this._tracer,
       this._closedWorld,
@@ -3372,8 +3378,8 @@
     FunctionEntity helperElement = _commonElements.findType;
     _registry.registerStaticUse(
         new StaticUse.staticInvoke(helperElement, CallStructure.ONE_ARG));
-    // TODO(sra): Encode recipe.
-    js.Expression recipe = js.string('${node.typeExpression}');
+    js.Expression recipe = _rtiRecipeEncoder.encodeGroundRecipe(
+        _emitter, TypeExpressionRecipe(node.typeExpression));
     js.Expression helper = _emitter.staticFunctionAccess(helperElement);
     push(js.js(r'#(#)', [helper, recipe]).withSourceInformation(
         node.sourceInformation));
@@ -3384,8 +3390,8 @@
     // Call `env._eval("recipe")`.
     use(node.inputs[0]);
     js.Expression environment = pop();
-    // TODO(sra): Encode recipe.
-    js.Expression recipe = js.string('${node.typeExpression}');
+    js.Expression recipe = _rtiRecipeEncoder.encodeRecipe(
+        _emitter, node.envStructure, node.typeExpression);
 
     MemberEntity method = _commonElements.rtiEvalMethod;
     Selector selector = Selector.fromElement(method);
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index 906f4a6..5396d8a 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -741,7 +741,7 @@
       } else {
         i++;
         switch (ch) {
-          case Recipe.noOp:
+          case Recipe.separator:
             break;
 
           case Recipe.toType:
diff --git a/sdk/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart b/sdk/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart
index 78d0975..361652d 100644
--- a/sdk/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart
+++ b/sdk/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart
@@ -12,18 +12,33 @@
 
   // Operators.
 
-  static const int noOp = _comma;
+  static const int separator = _comma;
 
   static const int toType = _semicolon;
 
   static const int pushDynamic = _at;
   static const int pushVoid = _tilde;
-  static const int wrapFutureOr = _formfeed;
+
+  static const int wrapStar = _asterisk;
+  static const int wrapQuestion = _question;
+  static const int wrapFutureOr = _slash;
 
   static const int startTypeArguments = _lessThan;
   static const int endTypeArguments = _greaterThan;
 
+  static const int startFunctionArguments = _leftParen;
+  static const int endFunctionArguments = _rightParen;
+  static const int startOptionalGroup = _leftBracket;
+  static const int endOptionalGroup = _rightBracket;
+  static const int startNamedGroup = _leftBrace;
+  static const int endNamedGroup = _rightBrace;
+  static const int nameSeparator = _colon;
+
+  static const int genericFunctionTypeParameterIndex = _circumflex;
+
   static const int extensionOp = _ampersand;
+  static const int pushNeverExtension = 0;
+  static const int pushAnyExtension = 1;
 
   // Number and name components.
 
@@ -41,27 +56,48 @@
 
   static const int _formfeed = 0x0C; // '\f' in string literal.
 
+  static const int _space = 0x20;
+  static const int _exclamation = 0x21;
+  static const int _hash = 0x23;
   static const int _dollar = 0x24;
+  static const int _percent = 0x25;
   static const int _ampersand = 0x26;
-  static const int _plus = 0x2B;
-  static const int _comma = 0x2C;
-  static const int _period = 0x2E;
-  static const int _digit0 = 0x30;
-  static const int _digit9 = 0x39;
-  static const int _semicolon = 0x3B;
-  static const int _lessThan = 0x3C;
-  static const int _greaterThan = 0x3E;
-  static const int _question = 0x3f;
-  static const int _at = 0x40;
-
-  static const int _underscore = 0x5F;
-  static const int _lowercaseA = 0x61;
-  static const int _tilde = 0x7E;
-
+  static const int _apostrophe = 0x27;
   static const int _leftParen = 0x28;
   static const int _rightParen = 0x29;
+  static const int _asterisk = 0x2A;
+  static const int _plus = 0x2B;
+  static const int _comma = 0x2C;
+  static const int _minus = 0x2D;
+  static const int _period = 0x2E;
+  static const int _slash = 0x2F;
+
+  static const int _digit0 = 0x30;
+  static const int _digit9 = 0x39;
+
+  static const int _colon = 0x3A;
+  static const int _semicolon = 0x3B;
+  static const int _lessThan = 0x3C;
+  static const int _equals = 0x3D;
+  static const int _greaterThan = 0x3E;
+  static const int _question = 0x3F;
+  static const int _at = 0x40;
+
+  static const int _uppercaseA = 0x41;
+  static const int _uppercaseZ = 0x5A;
+
   static const int _leftBracket = 0x5B;
+  static const int _backslash = 0x5C;
   static const int _rightBracket = 0x5D;
+  static const int _circumflex = 0x5E;
+  static const int _underscore = 0x5F;
+  static const int _backtick = 0x60;
+
+  static const int _lowercaseA = 0x61;
+  static const int _lowercaseZ = 0x7A;
+
   static const int _leftBrace = 0x7B;
+  static const int _vertical = 0x7C;
   static const int _rightBrace = 0x7D;
+  static const int _tilde = 0x7E;
 }