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