anchor.diff:    ClassInfo visitClass(ir.Class clazz, {ClassEntity classEntity}) {
       // Omit class if it is not needed.
       ClassInfo classInfo = ClassInfo(
  -        name: clazz.name,
  -        isAbstract: clazz.isAbstract,
  -        outputUnit: _unitInfoForClass(classEntity));
  +        name: clazz.name, isAbstract: clazz.isAbstract, outputUnit: null);
       _entityToInfo[classEntity] = classInfo;
   
  -    int size = dumpInfoTask.sizeOf(classEntity);
       clazz.members.forEach((ir.Member member) {
  +      // clazz.members includes constructors
         MemberEntity memberEntity =
  -          environment.lookupClassMember(classEntity, member.name.text);
  +          environment.lookupLocalClassMember(classEntity, member.name.text) ??
  +              environment.lookupConstructor(classEntity, member.name.text);
         if (memberEntity == null) return;
  -      // Note: JWorld representations of a kernel member may omit the field or
  -      // getter. Filter those cases here.
  -      if ((member is ir.Field && memberEntity is! FieldEntity) ||
  -          (member is ir.Procedure && memberEntity is! FunctionEntity)) {
  +      // Multiple kernel members can map to single JWorld member
  +      // (e.g., when one of a getter/field pair are tree-shaken),
  +      // so avoid duplicating the downstream info object.
  +      if (_entityToInfo.containsKey(memberEntity)) {
           return;
         }
   
         if (member.function != null) {
  -        FunctionInfo functionInfo = visitFunction(memberEntity);
  +        FunctionInfo functionInfo =
  +            visitFunction(member.function, functionEntity: memberEntity);
           if (functionInfo != null) {
             classInfo.functions.add(functionInfo);
             functionInfo.parent = classInfo;
  -          for (var closureInfo in functionInfo.closures) {
  -            size += closureInfo.size;
  -          }
           }
         } else {
           FieldInfo fieldInfo = visitField(member, fieldEntity: memberEntity);
           if (fieldInfo != null) {
             classInfo.fields.add(fieldInfo);
             fieldInfo.parent = classInfo;
  -          for (var closureInfo in fieldInfo.closures) {
  -            size += closureInfo.size;
  -          }
           }
         }
       });
  -
  -    clazz.constructors.forEach((ir.Constructor constructor) {
  -      final constructorEntity =
  -          environment.lookupConstructor(classEntity, constructor.name.text);
  -      if (constructorEntity == null) return;
  -      FunctionInfo functionInfo = visitFunction(constructorEntity);
  -      if (functionInfo != null) {
  -        classInfo.functions.add(functionInfo);
  -        functionInfo.parent = classInfo;
  -        for (var closureInfo in functionInfo.closures) {
  -          size += closureInfo.size;
  -        }
  -      }
  -    });
  -
  -    classInfo.size = size;
  -
  -    if (!compiler.backendStrategy.emitterTask.neededClasses
  -            .contains(classEntity) &&
  -        classInfo.fields.isEmpty &&
  -        classInfo.functions.isEmpty) {
  -      return null;
  -    }
   
       result.classes.add(classInfo);
       return classInfo;
     }
   
  -  ClosureInfo visitClosureClass(ClassEntity element) {
  -    ClosureInfo closureInfo = ClosureInfo(
  -        name: element.name,
  -        outputUnit: _unitInfoForClass(element),
  -        size: dumpInfoTask.sizeOf(element));
  -    _entityToInfo[element] = closureInfo;
  -
  -    FunctionEntity callMethod = closedWorld.elementEnvironment
  -        .lookupClassMember(element, Identifiers.call);
  -
  -    FunctionInfo functionInfo = visitFunction(callMethod);
  -    if (functionInfo == null) return null;
  -    closureInfo.function = functionInfo;
  -    functionInfo.parent = closureInfo;
  -
  -    result.closures.add(closureInfo);
  -    return closureInfo;
  -  }
  -
  -  FunctionInfo visitFunction(FunctionEntity function) {
  -    int size = dumpInfoTask.sizeOf(function);
  -    // TODO(sigmund): consider adding a small info to represent unreachable
  -    // code here.
  -    if (size == 0 && !shouldKeep(function)) return null;
  -
  -    // TODO(het): use 'toString' instead of 'text'? It will add '=' for setters
  -    String name = function.memberName.text;
  +  FunctionInfo visitFunction(ir.FunctionNode function,
  +      {FunctionEntity functionEntity}) {
  +    var parent = function.parent;
  +    String name = parent.toStringInternal();
  +    bool isConstructor = parent is ir.Constructor;
  +    bool isFactory = parent is ir.Procedure && parent.isFactory;
  +    // Kernel `isStatic` refers to static members, constructors, and top-level
  +    // members.
  +    bool isTopLevel = ((parent is ir.Field && parent.isStatic) ||
  +            (parent is ir.Procedure && parent.isStatic)) &&
  +        (parent is ir.Member && parent.enclosingClass == null);
  +    bool isStaticMember = ((parent is ir.Field && parent.isStatic) ||
  +            (parent is ir.Procedure && parent.isStatic)) &&
  +        (parent is ir.Member && parent.enclosingClass != null) &&
  +        !isConstructor &&
  +        !isFactory;
  +    bool isConst = parent is ir.Member && parent.isConst;
  +    bool isExternal = parent is ir.Member && parent.isExternal;
  +    bool isMethod = parent is ir.Member && parent.enclosingClass != null;
  +    bool isGetter = parent is ir.Procedure && parent.isGetter;
  +    bool isSetter = parent is ir.Procedure && parent.isSetter;
       int kind;
  -    if (function.isStatic || function.isTopLevel) {
  +    if (isTopLevel) {
         kind = FunctionInfo.TOP_LEVEL_FUNCTION_KIND;
  -    } else if (function.enclosingClass != null) {
  +    } else if (isMethod) {
         kind = FunctionInfo.METHOD_FUNCTION_KIND;
       }
  -
  -    if (function.isConstructor) {
  -      name = name == ""
  -          ? "${function.enclosingClass.name}"
  -          : "${function.enclosingClass.name}.${function.name}";
  +    if (isConstructor || isFactory) {
         kind = FunctionInfo.CONSTRUCTOR_FUNCTION_KIND;
  -    }
  -
  -    assert(kind != null);
  +      String functionName = function.toStringInternal();
  +      name = functionName.isEmpty ? '$name' : '$name$functionName';
  +    } else {
  +      if (parent.parent is ir.Class && name.contains('.')) {
  +        name = name.split('.')[1];
  +      }
  +    }
  +    if (name.endsWith('.')) name = name.substring(0, name.length - 1);
   
       FunctionModifiers modifiers = FunctionModifiers(
  -      isStatic: function.isStatic,
  -      isConst: function.isConst,
  -      isFactory: function.isConstructor
  -          ? (function as ConstructorEntity).isFactoryConstructor
  -          : false,
  -      isExternal: function.isExternal,
  +      isStatic: isStaticMember,
  +      isConst: isConst,
  +      isFactory: isFactory,
  +      isExternal: isExternal,
  +      isGetter: isGetter,
  +      isSetter: isSetter,
       );
  -    List<CodeSpan> code = dumpInfoTask.codeOf(function);
   
       List<ParameterInfo> parameters = <ParameterInfo>[];
       List<String> inferredParameterTypes = <String>[];
   
       closedWorld.elementEnvironment.forEachParameterAsLocal(
  -        _globalInferenceResults.globalLocalsMap, function, (parameter) {
  +        _globalInferenceResults.globalLocalsMap, functionEntity, (parameter) {
         inferredParameterTypes.add('${_resultOfParameter(parameter)}');
       });
  +
       int parameterIndex = 0;
  -    closedWorld.elementEnvironment.forEachParameter(function, (type, name, _) {
  +    closedWorld.elementEnvironment.forEachParameter(functionEntity,
  +        (type, name, _) {
         // Synthesized parameters have no name. This can happen on parameters of
         // setters derived from lowering late fields.
         parameters.add(ParameterInfo(name ?? '#t${parameterIndex}',
             inferredParameterTypes[parameterIndex++], '$type'));
       });
   
  -    var functionType = environment.getFunctionType(function);
  -    String returnType = '${functionType.returnType}';
  -
  -    String inferredReturnType = '${_resultOfMember(function).returnType}';
  -    String sideEffects =
  -        '${_globalInferenceResults.inferredData.getSideEffectsOfElement(function)}';
  -
  -    int inlinedCount = dumpInfoTask.inlineCount[function];
  -    if (inlinedCount == null) inlinedCount = 0;
  +    // TODO(markzipan): Determine if it's safe to default to nonNullable here.
  +    final nullability = parent is ir.Member
  +        ? parent.enclosingLibrary.nonNullable
  +        : ir.Nullability.nonNullable;
  +    final functionType = function.computeFunctionType(nullability);
   
       FunctionInfo info = FunctionInfo(
           name: name,
           functionKind: kind,
           modifiers: modifiers,
  -        returnType: returnType,
  -        inferredReturnType: inferredReturnType,
  +        returnType: function.returnType.toStringInternal(),
  +        inferredReturnType: null,
           parameters: parameters,
  -        sideEffects: sideEffects,
  -        inlinedCount: inlinedCount,
  -        code: code,
  -        type: functionType.toString(),
  -        outputUnit: _unitInfoForMember(function));
  -    _entityToInfo[function] = info;
  -
  -    int closureSize = _addClosureInfo(info, function);
  -    size += closureSize;
  +        sideEffects: null,
  +        inlinedCount: null,
  +        code: null,
  +        type: functionType.toStringInternal(),
  +        outputUnit: null);
  +    _entityToInfo[functionEntity] = info;
   
       if (compiler.options.experimentCallInstrumentation) {
         // We use function.hashCode because it is globally unique and it is
  
positive.code:       }
      });
  
      if (info.isEmpty && !shouldKeep(libEntity)) return null;
      result.libraries.add(info);
      return info;
    }
  
    GlobalTypeInferenceMemberResult _resultOfMember(MemberEntity e) =>
        _globalInferenceResults.resultOfMember(e);
  
    AbstractValue _resultOfParameter(Local e) =>
        _globalInferenceResults.resultOfParameter(e);
  
    FieldInfo visitField(ir.Field field, {FieldEntity fieldEntity}) {
      AbstractValue inferredType = _resultOfMember(fieldEntity).type;
      // If a field has an empty inferred type it is never used.
      if (inferredType == null ||
          closedWorld.abstractValueDomain
              .isEmpty(inferredType)
              .isDefinitelyTrue) {
        return null;
      }
  
      int size = dumpInfoTask.sizeOf(fieldEntity);
      List<CodeSpan> code = dumpInfoTask.codeOf(fieldEntity);
  
      // TODO(het): Why doesn't `size` account for the code size already?
      if (code != null) size += code.length;
  
      FieldInfo info = FieldInfo(
          name: field.name.text,
          type: field.type.toStringInternal(),
          inferredType: '$inferredType',
          code: code,
          outputUnit: _unitInfoForMember(fieldEntity),
          isConst: field.isConst);
      _entityToInfo[fieldEntity] = info;
      FieldAnalysisData fieldData =
          closedWorld.fieldAnalysis.getFieldData(fieldEntity);
      if (fieldData.initialValue != null) {
        info.initializer = _constantToInfo[fieldData.initialValue];
      }
  
      if (compiler.options.experimentCallInstrumentation) {
        // We use field.hashCode because it is globally unique and it is
  
negative.code:       libname = '<unnamed>';
      }
  
      LibraryInfo info = LibraryInfo(libname, lib.importUri, null, null);
      _entityToInfo[libEntity] = info;
  
      lib.members.forEach((ir.Member member) {
        final memberEntity =
            environment.lookupLibraryMember(libEntity, member.name.text);
        if (memberEntity == null) return;
        if (member.function != null) {
          FunctionInfo functionInfo =
              visitFunction(member.function, functionEntity: memberEntity);
          if (functionInfo != null) {
            info.topLevelFunctions.add(functionInfo);
            functionInfo.parent = info;
  