anchor.diff:      return labels;
     },
   );
  +
  +String _typeToString(CandyType type) {
  +  String mapAndJoin(List<CandyType> types) =>
  +      types.map(_typeToString).join(', ');
  +
  +  return type.maybeMap(
  +    user: (it) {
  +      if (it.arguments.isEmpty) return it.name;
  +      return '${it.name}<${mapAndJoin(it.arguments)}>';
  +    },
  +    this_: (_) => 'This',
  +    tuple: (it) => '(${mapAndJoin(it.items)})',
  +    function: (it) {
  +      final receiver =
  +          it.receiverType == null ? '' : '${_typeToString(it.receiverType)}.';
  +      return '$receiver(${mapAndJoin(it.parameterTypes)}) => ${it.returnType}';
  +    },
  +    union: (it) => it.types.map(_typeToString).join(' | '),
  +    intersection: (it) => it.types.map(_typeToString).join(' & '),
  +    orElse: () => type.toString(),
  +  );
  +}
   
   class _BodyPropertyVisitor extends ast.TraversingAstVisitor {
     _BodyPropertyVisitor._(
  
positive.code:       final hir = getExpression(context, id);
        _labels.add(TypeLabel(
          node.name.span.toRange(server, resourceId),
          hir is Some ? hir.value.type.toString() : "(couldn't determine type)",
        ));
      }
      super.visitPropertyDeclarationExpression(node);
  
negative.code:         if (functionAst.returnType == null) {
            final range =
                functionAst.rightParenthesis.span.toRange(server, resourceId);
            labels.add(TypeLabel(range, _typeToString(functionHir.returnType)));
          }
  
          if (functionAst.body != null) handleBody(functionAst.body);
  