trphoenix
2018-11-12 29fbfc5dd1d55d189f23eb6d32f000252f92985f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright (c) 2018, 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.
 
import 'package:analyzer/dart/element/type.dart';
 
import '../constants.dart';
import '../shared_checkers.dart';
import '../type_helper.dart';
import '../utils.dart';
 
const _keyParam = 'k';
 
class MapHelper extends TypeHelper<TypeHelperContextWithConfig> {
  const MapHelper();
 
  @override
  String serialize(DartType targetType, String expression,
      TypeHelperContextWithConfig context) {
    if (!coreMapTypeChecker.isAssignableFromType(targetType)) {
      return null;
    }
    final args = typeArgumentsOf(targetType, coreMapTypeChecker);
    assert(args.length == 2);
 
    final keyType = args[0];
    final valueType = args[1];
 
    _checkSafeKeyType(expression, keyType);
 
    final subFieldValue = context.serialize(valueType, closureArg);
    final subKeyValue = context.serialize(keyType, _keyParam);
 
    if (closureArg == subFieldValue && _keyParam == subKeyValue) {
      return expression;
    }
 
    if (context.config.useWrappers) {
      var method = '\$wrapMap';
      if (context.nullable) {
        method = '${method}HandleNull';
      }
 
      final lambda = LambdaResult.process(subFieldValue, closureArg);
      return '$method<$keyType, $valueType>($expression, $lambda)';
    }
 
    final optionalQuestion = context.nullable ? '?' : '';
 
    return '$expression$optionalQuestion'
        '.map(($_keyParam, $closureArg) => MapEntry($subKeyValue, $subFieldValue))';
  }
 
  @override
  String deserialize(DartType targetType, String expression,
      TypeHelperContextWithConfig context) {
    if (!coreMapTypeChecker.isAssignableFromType(targetType)) {
      return null;
    }
 
    final typeArgs = typeArgumentsOf(targetType, coreMapTypeChecker);
    assert(typeArgs.length == 2);
    final keyArg = typeArgs.first;
    final valueArg = typeArgs.last;
 
    _checkSafeKeyType(expression, keyArg);
 
    final valueArgIsAny = _isObjectOrDynamic(valueArg);
    final isEnumKey = isEnum(keyArg);
 
    if (!isEnumKey) {
      if (valueArgIsAny) {
        if (context.config.anyMap) {
          if (_isObjectOrDynamic(keyArg)) {
            return '$expression as Map';
          }
        } else {
          // this is the trivial case. Do a runtime cast to the known type of JSON
          // map values - `Map<String, dynamic>`
          return '$expression as Map<String, dynamic>';
        }
      }
 
      if (!context.nullable &&
          (valueArgIsAny ||
              simpleJsonTypeChecker.isAssignableFromType(valueArg))) {
        // No mapping of the values or null check required!
        return 'Map<String, $valueArg>.from($expression as Map)';
      }
    }
 
    // In this case, we're going to create a new Map with matching reified
    // types.
 
    final itemSubVal = context.deserialize(valueArg, closureArg);
 
    final optionalQuestion = context.nullable ? '?' : '';
 
    final mapCast =
        context.config.anyMap ? 'as Map' : 'as Map<String, dynamic>';
 
    String keyUsage;
    if (isEnumKey) {
      keyUsage = context.deserialize(keyArg, _keyParam).toString();
    } else if (context.config.anyMap && !_isObjectOrDynamic(keyArg)) {
      keyUsage = '$_keyParam as String';
    } else {
      keyUsage = _keyParam;
    }
 
    return '($expression $mapCast)$optionalQuestion.map('
        '($_keyParam, $closureArg) => MapEntry($keyUsage, $itemSubVal))';
  }
}
 
bool _isObjectOrDynamic(DartType type) => type.isObject || type.isDynamic;
 
void _checkSafeKeyType(String expression, DartType keyArg) {
  // We're not going to handle converting key types at the moment
  // So the only safe types for key are dynamic/Object/String/enum
  final safeKey = _isObjectOrDynamic(keyArg) ||
      coreStringTypeChecker.isExactlyType(keyArg) ||
      isEnum(keyArg);
 
  if (!safeKey) {
    throw UnsupportedTypeError(keyArg, expression,
        'Map keys must be of type `String`, enum, `Object` or `dynamic`.');
  }
}