dart_generator.py revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""
5Generator language component for compiler.py that adds Dart language support.
6"""
7
8from code import Code
9from model import *
10from schema_util import *
11
12import os
13from datetime import datetime
14
15LICENSE = (
16"""// Copyright (c) %s, the Dart project authors.  Please see the AUTHORS file
17// for details. All rights reserved. Use of this source code is governed by a
18// BSD-style license that can be found in the LICENSE file.""" %
19    datetime.now().year)
20
21class DartGenerator(object):
22  def __init__(self, dart_overrides_dir=None):
23    self._dart_overrides_dir = dart_overrides_dir
24
25  def Generate(self, namespace):
26    return _Generator(namespace, self._dart_overrides_dir).Generate()
27
28class _Generator(object):
29  """A .dart generator for a namespace.
30  """
31
32  def __init__(self, namespace, dart_overrides_dir=None):
33    self._namespace = namespace
34    # TODO(sashab): Once inline type definitions start being added to
35    # self._types, make a _FindType(self, type_) function that looks at
36    # self._namespace.types.
37    self._types = namespace.types
38
39    # Build a dictionary of Type Name --> Custom Dart code.
40    self._type_overrides = {}
41    if dart_overrides_dir is not None:
42      for filename in os.listdir(dart_overrides_dir):
43        if filename.startswith(namespace.unix_name):
44          with open(os.path.join(dart_overrides_dir, filename)) as f:
45            # Split off the namespace and file extension, leaving just the type.
46            type_path = '.'.join(filename.split('.')[1:-1])
47            self._type_overrides[type_path] = f.read()
48
49    # TODO(sashab): Add all inline type definitions to the global Types
50    # dictionary here, so they have proper names, and are implemented along with
51    # all other types. Also update the parameters/members with these types
52    # to reference these new types instead.
53
54  def Generate(self):
55    """Generates a Code object with the .dart for the entire namespace.
56    """
57    c = Code()
58    (c.Append(LICENSE)
59      .Append()
60      .Append('// Generated from namespace: %s' % self._namespace.name)
61      .Append()
62      .Append('part of chrome;'))
63
64    if self._types:
65      (c.Append()
66        .Append('/**')
67        .Append(' * Types')
68        .Append(' */')
69        .Append()
70      )
71    for type_ in self._types.values():
72      # Check for custom dart for this whole type.
73      override = self._GetOverride([type_.name], document_with=type_)
74      c.Cblock(override if override is not None else self._GenerateType(type_))
75
76    if self._namespace.events:
77      (c.Append('/**')
78        .Append(' * Events')
79        .Append(' */')
80        .Append()
81      )
82    for event_name in self._namespace.events:
83      c.Cblock(self._GenerateEvent(self._namespace.events[event_name]))
84
85    (c.Append('/**')
86      .Append(' * Functions')
87      .Append(' */')
88      .Append()
89    )
90    c.Cblock(self._GenerateMainClass())
91
92    return c
93
94  def _GenerateType(self, type_):
95    """Given a Type object, returns the Code with the .dart for this
96    type's definition.
97
98    Assumes this type is a Parameter Type (creatable by user), and creates an
99    object that extends ChromeObject. All parameters are specifiable as named
100    arguments in the constructor, and all methods are wrapped with getters and
101    setters that hide the JS() implementation.
102    """
103    c = Code()
104
105    # Since enums are just treated as strings for now, don't generate their
106    # type.
107    # TODO(sashab): Find a nice way to wrap enum objects.
108    if type_.property_type is PropertyType.ENUM:
109      return c
110
111    (c.Concat(self._GenerateDocumentation(type_))
112      .Sblock('class %(type_name)s extends ChromeObject {')
113    )
114
115    # Check whether this type has function members. If it does, don't allow
116    # public construction.
117    add_public_constructor = all(not self._IsFunction(p.type_)
118                                 for p in type_.properties.values())
119    constructor_fields = [self._GeneratePropertySignature(p)
120                          for p in type_.properties.values()]
121
122    if add_public_constructor:
123      (c.Append('/*')
124        .Append(' * Public constructor')
125        .Append(' */')
126        .Sblock('%(type_name)s({%(constructor_fields)s}) {')
127      )
128
129      for prop_name in type_.properties:
130        (c.Sblock('if (?%s)' % prop_name)
131          .Append('this.%s = %s;' % (prop_name, prop_name))
132          .Eblock()
133        )
134      (c.Eblock('}')
135        .Append()
136      )
137
138    (c.Append('/*')
139      .Append(' * Private constructor')
140      .Append(' */')
141      .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);')
142    )
143
144    # Add an accessor (getter & setter) for each property.
145    properties = [p for p in type_.properties.values()
146                  if not self._IsFunction(p.type_)]
147    if properties:
148      (c.Append()
149        .Append('/*')
150        .Append(' * Public accessors')
151        .Append(' */')
152      )
153    for prop in properties:
154      override = self._GetOverride([type_.name, prop.name], document_with=prop)
155      c.Concat(override if override is not None
156               else self._GenerateGetterAndSetter(type_, prop))
157
158    # Now add all the methods.
159    methods = [t for t in type_.properties.values()
160               if self._IsFunction(t.type_)]
161    if methods:
162      (c.Append()
163        .Append('/*')
164        .Append(' * Methods')
165        .Append(' */')
166      )
167    for prop in methods:
168      # Check if there's an override for this method.
169      override = self._GetOverride([type_.name, prop.name], document_with=prop)
170      c.Cblock(override if override is not None
171               else self._GenerateFunction(prop.type_.function))
172
173    (c.Eblock('}')
174      .Substitute({
175        'type_name': self._AddPrefix(type_.simple_name),
176        'constructor_fields': ', '.join(constructor_fields)
177      })
178    )
179
180    return c
181
182  def _GenerateGetterAndSetter(self, type_, prop):
183    """Given a Type and Property, returns the Code object for the getter and
184    setter for that property.
185    """
186    c = Code()
187    override = self._GetOverride([type_.name, prop.name, '.get'],
188                                 document_with=prop)
189    c.Cblock(override if override is not None
190             else self._GenerateGetter(type_, prop))
191    override = self._GetOverride([type_.name, prop.name, '.set'])
192    c.Cblock(override if override is not None
193             else self._GenerateSetter(type_, prop))
194    return c
195
196  def _GenerateGetter(self, type_, prop):
197    """Given a Type and Property, returns the Code object for the getter for
198    that property.
199
200    Also adds the documentation for this property before the method.
201    """
202    c = Code()
203    c.Concat(self._GenerateDocumentation(prop))
204
205    type_name = self._GetDartType(prop.type_)
206    if (self._IsBaseType(prop.type_)):
207      c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
208          (type_name, prop.name, type_name, prop.name))
209    elif self._IsSerializableObjectType(prop.type_):
210      c.Append("%s get %s => new %s._proxy(JS('', '#.%s', "
211               "this._jsObject));" %
212          (type_name, prop.name, type_name, prop.name))
213    elif self._IsListOfSerializableObjects(prop.type_):
214      (c.Sblock('%s get %s {' % (type_name, prop.name))
215        .Append('%s __proxy_%s = new %s();' % (type_name, prop.name,
216                                               type_name))
217        .Append("int count = JS('int', '#.%s.length', this._jsObject);" %
218            prop.name)
219        .Sblock("for (int i = 0; i < count; i++) {")
220        .Append("var item = JS('', '#.%s', this._jsObject);" % prop.name)
221        .Append('__proxy_%s.add(new %s._proxy(item));' % (prop.name,
222            self._GetDartType(prop.type_.item_type)))
223        .Eblock('}')
224        .Append('return __proxy_%s;' % prop.name)
225        .Eblock('}')
226      )
227    elif self._IsObjectType(prop.type_):
228      # TODO(sashab): Think of a way to serialize generic Dart objects.
229      if type_name in self._types:
230        c.Append("%s get %s => new %s._proxy(JS('%s', '#.%s', "
231                 "this._jsObject));" %
232            (type_name, prop.name, type_name, type_name, prop.name))
233      else:
234        c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" %
235            (type_name, prop.name, type_name, prop.name))
236    else:
237      raise Exception(
238          "Could not generate wrapper for %s.%s: unserializable type %s" %
239          (type_.name, prop.name, type_name)
240      )
241    return c
242
243  def _GenerateSetter(self, type_, prop):
244    """Given a Type and Property, returns the Code object for the setter for
245    that property.
246    """
247    c = Code()
248    type_name = self._GetDartType(prop.type_)
249    wrapped_name = prop.name
250    if not self._IsBaseType(prop.type_):
251      wrapped_name = 'convertArgument(%s)' % prop.name
252
253    (c.Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name))
254      .Append("JS('void', '#.%s = #', this._jsObject, %s);" %
255        (prop.name, wrapped_name))
256      .Eblock("}")
257    )
258    return c
259
260  def _GenerateDocumentation(self, prop):
261    """Given an object, generates the documentation for this object (as a
262    code string) and returns the Code object.
263
264    Returns an empty code object if the object has no documentation.
265
266    Uses triple-quotes for the string.
267    """
268    c = Code()
269    if prop.description is not None:
270      for line in prop.description.split('\n'):
271        c.Comment(line, comment_prefix='/// ')
272    return c
273
274  def _GenerateFunction(self, f):
275    """Returns the Code object for the given function.
276    """
277    c = Code()
278    c.Concat(self._GenerateDocumentation(f))
279
280    if not self._NeedsProxiedCallback(f):
281        c.Append("%s => %s;" % (self._GenerateFunctionSignature(f),
282                               self._GenerateProxyCall(f)))
283        return c
284
285    (c.Sblock("%s {" % self._GenerateFunctionSignature(f))
286      .Concat(self._GenerateProxiedFunction(f.callback, f.callback.name))
287      .Append('%s;' % self._GenerateProxyCall(f))
288      .Eblock('}')
289    )
290
291    return c
292
293  def _GenerateProxiedFunction(self, f, callback_name):
294    """Given a function (assumed to be a callback), generates the proxied
295    version of this function, which calls |callback_name| if it is defined.
296
297    Returns a Code object.
298    """
299    c = Code()
300    proxied_params = []
301    # A list of Properties, containing List<*> objects that need proxying for
302    # their members (by copying out each member and proxying it).
303    lists_to_proxy = []
304    for p in f.params:
305      if self._IsBaseType(p.type_):
306        proxied_params.append(p.name)
307      elif self._IsSerializableObjectType(p.type_):
308        proxied_params.append('new %s._proxy(%s)' % (
309            self._GetDartType(p.type_), p.name))
310      elif self._IsListOfSerializableObjects(p.type_):
311        proxied_params.append('__proxy_%s' % p.name)
312        lists_to_proxy.append(p)
313      elif self._IsObjectType(p.type_):
314        # TODO(sashab): Find a way to build generic JS objects back in Dart.
315        proxied_params.append('%s' % p.name)
316      elif p.type_.property_type is PropertyType.ARRAY:
317        # TODO(sashab): This might be okay - what if this is a list of
318        # FileEntry elements? In this case, a basic list will proxy the objects
319        # fine.
320        proxied_params.append('%s' % p.name)
321      else:
322        raise Exception(
323            "Cannot automatically create proxy; can't wrap %s, type %s" % (
324                self._GenerateFunctionSignature(f), self._GetDartType(p.type_)))
325
326    (c.Sblock("void __proxy_callback(%s) {" % ', '.join(p.name for p in
327                                               f.params))
328      .Sblock('if (?%s) {' % callback_name)
329    )
330
331    # Add the proxied lists.
332    for list_to_proxy in lists_to_proxy:
333      (c.Append("%s __proxy_%s = new %s();" % (
334                    self._GetDartType(list_to_proxy.type_),
335                    list_to_proxy.name,
336                    self._GetDartType(list_to_proxy.type_)))
337        .Sblock("for (var o in %s) {" % list_to_proxy.name)
338        .Append('__proxy_%s.add(new %s._proxy(o));' % (list_to_proxy.name,
339            self._GetDartType(list_to_proxy.type_.item_type)))
340        .Eblock("}")
341      )
342
343    (c.Append("%s(%s);" % (callback_name, ', '.join(proxied_params)))
344      .Eblock('}')
345      .Eblock('}')
346    )
347    return c
348
349  def _NeedsProxiedCallback(self, f):
350    """Given a function, returns True if this function's callback needs to be
351    proxied, False if not.
352
353    Function callbacks need to be proxied if they have at least one
354    non-base-type parameter.
355    """
356    return f.callback and self._NeedsProxy(f.callback)
357
358  def _NeedsProxy(self, f):
359    """Given a function, returns True if it needs to be proxied, False if not.
360
361    A function needs to be proxied if any of its members are non-base types.
362    This means that, when the function object is passed to Javascript, it
363    needs to be wrapped in a "proxied" call that converts the JS inputs to Dart
364    objects explicitly, before calling the real function with these new objects.
365    """
366    return any(not self._IsBaseType(p.type_) for p in f.params)
367
368  def _GenerateProxyCall(self, function, call_target='this._jsObject'):
369    """Given a function, generates the code to call that function via JS().
370    Returns a string.
371
372    |call_target| is the name of the object to call the function on. The default
373    is this._jsObject.
374
375    e.g.
376        JS('void', '#.resizeTo(#, #)', this._jsObject, width, height)
377        JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds))
378    """
379    n_params = len(function.params)
380    if function.callback:
381      n_params += 1
382
383    return_type_str = self._GetDartType(function.returns)
384    params = []
385
386    # If this object is serializable, don't convert the type from JS - pass the
387    # JS object straight into the proxy.
388    if self._IsSerializableObjectType(function.returns):
389      params.append("''")
390    else:
391      params.append("'%s'" % return_type_str)
392
393    params.append("'#.%s(%s)'" % (function.name, ', '.join(['#'] * n_params)))
394    params.append(call_target)
395
396    for param in function.params:
397      if not self._IsBaseType(param.type_):
398        params.append('convertArgument(%s)' % param.name)
399      else:
400        params.append(param.name)
401    if function.callback:
402      # If this isn't a base type, we need a proxied callback.
403      callback_name = function.callback.name
404      if self._NeedsProxiedCallback(function):
405        callback_name = "__proxy_callback"
406      params.append('convertDartClosureToJS(%s, %s)' % (callback_name,
407                    len(function.callback.params)))
408
409    # If the object is serializable, call the proxy constructor for this type.
410    proxy_call = 'JS(%s)' % ', '.join(params)
411    if self._IsSerializableObjectType(function.returns):
412      proxy_call = 'new %s._proxy(%s)' % (return_type_str, proxy_call)
413
414    return proxy_call
415
416  def _GenerateEvent(self, event):
417    """Given a Function object, returns the Code with the .dart for this event,
418    represented by the function.
419
420    All events extend the Event base type.
421    """
422    c = Code()
423
424    # Add documentation for this event.
425    (c.Concat(self._GenerateDocumentation(event))
426      .Sblock('class Event_%(event_name)s extends Event {')
427    )
428
429    # If this event needs a proxy, all calls need to be proxied.
430    needs_proxy = self._NeedsProxy(event)
431
432    # Override Event callback type definitions.
433    for ret_type, event_func in (('void', 'addListener'),
434                                 ('void', 'removeListener'),
435                                 ('bool', 'hasListener')):
436      param_list = self._GenerateParameterList(event.params, event.callback,
437                                               convert_optional=True)
438      if needs_proxy:
439        (c.Sblock('%s %s(void callback(%s)) {' % (ret_type, event_func,
440                                                 param_list))
441          .Concat(self._GenerateProxiedFunction(event, 'callback'))
442          .Append('super.%s(__proxy_callback);' % event_func)
443          .Eblock('}')
444        )
445      else:
446        c.Append('%s %s(void callback(%s)) => super.%s(callback);' %
447            (ret_type, event_func, param_list, event_func))
448      c.Append()
449
450    # Generate the constructor.
451    (c.Append('Event_%(event_name)s(jsObject) : '
452              'super._(jsObject, %(param_num)d);')
453      .Eblock('}')
454      .Substitute({
455        'event_name': self._namespace.unix_name + '_' + event.name,
456        'param_num': len(event.params)
457      })
458    )
459
460    return c
461
462  def _GenerateMainClass(self):
463    """Generates the main class for this file, which links to all functions
464    and events.
465
466    Returns a code object.
467    """
468    c = Code()
469    (c.Sblock('class API_%s {' % self._namespace.unix_name)
470      .Append('/*')
471      .Append(' * API connection')
472      .Append(' */')
473      .Append('Object _jsObject;')
474    )
475
476    # Add events.
477    if self._namespace.events:
478      (c.Append()
479        .Append('/*')
480        .Append(' * Events')
481        .Append(' */')
482      )
483    for event_name in self._namespace.events:
484      c.Append('Event_%s_%s %s;' % (self._namespace.unix_name, event_name,
485                                    event_name))
486
487    # Add functions.
488    if self._namespace.functions:
489      (c.Append()
490        .Append('/*')
491        .Append(' * Functions')
492        .Append(' */')
493      )
494    for function in self._namespace.functions.values():
495      # Check for custom dart for this whole property.
496      override = self._GetOverride([function.name], document_with=function)
497      c.Cblock(override if override is not None
498               else self._GenerateFunction(function))
499
500    # Add the constructor.
501    c.Sblock('API_%s(this._jsObject) {' % self._namespace.unix_name)
502
503    # Add events to constructor.
504    for event_name in self._namespace.events:
505      c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" %
506        (event_name, self._namespace.unix_name, event_name, event_name))
507
508    (c.Eblock('}')
509      .Eblock('}')
510    )
511    return c
512
513  def _GeneratePropertySignature(self, prop):
514    """Given a property, returns a signature for that property.
515    Recursively generates the signature for callbacks.
516    Returns a String for the given property.
517
518    e.g.
519      bool x
520      void onClosed()
521      void doSomething(bool x, void callback([String x]))
522    """
523    if self._IsFunction(prop.type_):
524      return self._GenerateFunctionSignature(prop.type_.function)
525    return '%(type)s %(name)s' % {
526               'type': self._GetDartType(prop.type_),
527               'name': prop.simple_name
528           }
529
530  def _GenerateFunctionSignature(self, function, convert_optional=False):
531    """Given a function object, returns the signature for that function.
532    Recursively generates the signature for callbacks.
533    Returns a String for the given function.
534
535    If convert_optional is True, changes optional parameters to be required.
536
537    e.g.
538      void onClosed()
539      bool isOpen([String type])
540      void doSomething(bool x, void callback([String x]))
541    """
542    sig = '%(return_type)s %(name)s(%(params)s)'
543
544    if function.returns:
545      return_type = self._GetDartType(function.returns)
546    else:
547      return_type = 'void'
548
549    return sig % {
550        'return_type': return_type,
551        'name': function.simple_name,
552        'params': self._GenerateParameterList(function.params,
553                                              function.callback,
554                                              convert_optional=convert_optional)
555    }
556
557  def _GenerateParameterList(self,
558                             params,
559                             callback=None,
560                             convert_optional=False):
561    """Given a list of function parameters, generates their signature (as a
562    string).
563
564    e.g.
565      [String type]
566      bool x, void callback([String x])
567
568    If convert_optional is True, changes optional parameters to be required.
569    Useful for callbacks, where optional parameters are treated as required.
570    """
571    # Params lists (required & optional), to be joined with commas.
572    # TODO(sashab): Don't assume optional params always come after required
573    # ones.
574    params_req = []
575    params_opt = []
576    for param in params:
577      p_sig = self._GeneratePropertySignature(param)
578      if param.optional and not convert_optional:
579        params_opt.append(p_sig)
580      else:
581        params_req.append(p_sig)
582
583    # Add the callback, if it exists.
584    if callback:
585      c_sig = self._GenerateFunctionSignature(callback, convert_optional=True)
586      if callback.optional:
587        params_opt.append(c_sig)
588      else:
589        params_req.append(c_sig)
590
591    # Join the parameters with commas.
592    # Optional parameters have to be in square brackets, e.g.:
593    #
594    #  required params | optional params |     output
595    #        []        |        []       |       ''
596    #      [x, y]      |        []       |     'x, y'
597    #        []        |      [a, b]     |    '[a, b]'
598    #      [x, y]      |      [a, b]     | 'x, y, [a, b]'
599    if params_opt:
600      params_opt[0] = '[%s' % params_opt[0]
601      params_opt[-1] = '%s]' % params_opt[-1]
602    param_sets = [', '.join(params_req), ', '.join(params_opt)]
603
604    # The 'if p' part here is needed to prevent commas where there are no
605    # parameters of a certain type.
606    # If there are no optional parameters, this prevents a _trailing_ comma,
607    # e.g. '(x, y,)'. Similarly, if there are no required parameters, this
608    # prevents a leading comma, e.g. '(, [a, b])'.
609    return ', '.join(p for p in param_sets if p)
610
611  def _GetOverride(self, key_chain, document_with=None):
612    """Given a list of keys, joins them with periods and searches for them in
613    the custom dart overrides.
614    If there is an override for that key, finds the override code and returns
615    the Code object. If not, returns None.
616
617    If document_with is not None, adds the documentation for this property
618    before the override code.
619    """
620    c = Code()
621    contents = self._type_overrides.get('.'.join(key_chain))
622    if contents is None:
623      return None
624
625    if document_with is not None:
626      c.Concat(self._GenerateDocumentation(document_with))
627    for line in contents.strip('\n').split('\n'):
628      c.Append(line)
629    return c
630
631  def _AddPrefix(self, name):
632    """Given the name of a type, prefixes the namespace (as camelcase) and
633    returns the new name.
634    """
635    # TODO(sashab): Split the dart library into multiple files, avoiding the
636    # need for this prefixing.
637    return ('%s%s' % (
638        ''.join(s.capitalize() for s in self._namespace.name.split('.')),
639        name))
640
641  def _IsFunction(self, type_):
642    """Given a model.Type, returns whether this type is a function.
643    """
644    return type_.property_type == PropertyType.FUNCTION
645
646  def _IsSerializableObjectType(self, type_):
647    """Given a model.Type, returns whether this type is a serializable object.
648    Serializable objects are custom types defined in this namespace.
649
650    If this object is a reference to something not in this namespace, assumes
651    its a serializable object.
652    """
653    if type_ is None:
654      return False
655    if type_.property_type is PropertyType.CHOICES:
656      return all(self._IsSerializableObjectType(c) for c in type_.choices)
657    if type_.property_type is PropertyType.REF:
658      if type_.ref_type in self._types:
659        return self._IsObjectType(self._types[type_.ref_type])
660      return True
661    if (type_.property_type == PropertyType.OBJECT
662        and type_.instance_of in self._types):
663      return self._IsObjectType(self._types[type_.instance_of])
664    return False
665
666  def _IsObjectType(self, type_):
667    """Given a model.Type, returns whether this type is an object.
668    """
669    return (self._IsSerializableObjectType(type_)
670            or type_.property_type in [PropertyType.OBJECT, PropertyType.ANY])
671
672  def _IsListOfSerializableObjects(self, type_):
673    """Given a model.Type, returns whether this type is a list of serializable
674    objects (or regular objects, if this list is treated as a type - in this
675    case, the item type was defined inline).
676
677    If this type is a reference to something not in this namespace, assumes
678    it is not a list of serializable objects.
679    """
680    if type_.property_type is PropertyType.CHOICES:
681      return all(self._IsListOfSerializableObjects(c) for c in type_.choices)
682    if type_.property_type is PropertyType.REF:
683      if type_.ref_type in self._types:
684        return self._IsListOfSerializableObjects(self._types[type_.ref_type])
685      return False
686    return (type_.property_type is PropertyType.ARRAY and
687        (self._IsSerializableObjectType(type_.item_type)))
688
689  def _IsListOfBaseTypes(self, type_):
690    """Given a model.Type, returns whether this type is a list of base type
691    objects (PropertyType.REF types).
692    """
693    if type_.property_type is PropertyType.CHOICES:
694      return all(self._IsListOfBaseTypes(c) for c in type_.choices)
695    return (type_.property_type is PropertyType.ARRAY and
696            self._IsBaseType(type_.item_type))
697
698  def _IsBaseType(self, type_):
699    """Given a model.type_, returns whether this type is a base type
700    (string, number, boolean, or a list of these).
701
702    If type_ is a Choices object, returns True if all possible choices are base
703    types.
704    """
705    # TODO(sashab): Remove 'Choices' as a base type once they are wrapped in
706    # native Dart classes.
707    if type_.property_type is PropertyType.CHOICES:
708      return all(self._IsBaseType(c) for c in type_.choices)
709    return (
710        (self._GetDartType(type_) in ['bool', 'num', 'int', 'double', 'String'])
711        or (type_.property_type is PropertyType.ARRAY
712            and self._IsBaseType(type_.item_type))
713    )
714
715  def _GetDartType(self, type_):
716    """Given a model.Type object, returns its type as a Dart string.
717    """
718    if type_ is None:
719      return 'void'
720
721    prop_type = type_.property_type
722    if prop_type is PropertyType.REF:
723      if type_.ref_type in self._types:
724        return self._GetDartType(self._types[type_.ref_type])
725      # TODO(sashab): If the type is foreign, it might have to be imported.
726      return StripNamespace(type_.ref_type)
727    elif prop_type is PropertyType.BOOLEAN:
728      return 'bool'
729    elif prop_type is PropertyType.INTEGER:
730      return 'int'
731    elif prop_type is PropertyType.INT64:
732      return 'num'
733    elif prop_type is PropertyType.DOUBLE:
734      return 'double'
735    elif prop_type is PropertyType.STRING:
736      return 'String'
737    elif prop_type is PropertyType.ENUM:
738      return 'String'
739    elif prop_type is PropertyType.CHOICES:
740      # TODO(sashab): Think of a nice way to generate code for Choices objects
741      # in Dart.
742      return 'Object'
743    elif prop_type is PropertyType.ANY:
744      return 'Object'
745    elif prop_type is PropertyType.OBJECT:
746      # TODO(sashab): type_.name is the name of the function's parameter for
747      # inline types defined in functions. Think of a way to generate names
748      # for this, or remove all inline type definitions at the start.
749      if type_.instance_of is not None:
750        return type_.instance_of
751      if not isinstance(type_.parent, Function):
752        return self._AddPrefix(type_.name)
753      return 'Object'
754    elif prop_type is PropertyType.FUNCTION:
755      return 'Function'
756    elif prop_type is PropertyType.ARRAY:
757      return 'List<%s>' % self._GetDartType(type_.item_type)
758    elif prop_type is PropertyType.BINARY:
759      return 'String'
760    else:
761      raise NotImplementedError(prop_type)
762
763