text: /* @flow */
  
  import config from 'core/config'
  import { addHandler, addProp, getBindingAttr } from 'compiler/helpers'
  import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
  
  let warn
  
  // in some cases, the event used has to be determined at runtime
  // so we used some reserved tokens during compile.
  export const RANGE_TOKEN = '__r'
  export const CHECKBOX_RADIO_TOKEN = '__c'
  
  export default function model (
      el: ASTElement,
      dir: ASTDirective,
      _warn: Function
  ): ?boolean {
      warn = _warn
      const value = dir.value
      const modifiers = dir.modifiers
      const tag = el.tag
      const type = el.attrsMap.type
  
      if (process.env.NODE_ENV !== 'production') {
          // inputs with type="file" are read only and setting the input's
          // value will throw an error.
          if (tag === 'input' && type === 'file') {
              warn(
                  `<${el.tag} v-model="${value}" type="file">:\n` +
                  `File inputs are read only. Use a v-on:change listener instead.`
              )
          }
      }
  
      if (el.component) {
          genComponentModel(el, value, modifiers)
          // component v-model doesn't need extra runtime
          return false
      } else if (tag === 'select') {
          genSelect(el, value, modifiers)
      } else if (tag === 'input' && type === 'checkbox') {
          genCheckboxModel(el, value, modifiers)
      } else if (tag === 'input' && type === 'radio') {
          genRadioModel(el, value, modifiers)
      } else if (tag === 'input' || tag === 'textarea') {
          genDefaultModel(el, value, modifiers)
      } else if (!config.isReservedTag(tag)) {
          genComponentModel(el, value, modifiers)
          // component v-model doesn't need extra runtime
          return false
      } else if (process.env.NODE_ENV !== 'production') {
          warn(
              `<${el.tag} v-model="${value}">: ` +
              `v-model is not supported on this element type. ` +
              'If you are working with contenteditable, it\'s recommended to ' +
              'wrap a library dedicated for that purpose inside a custom component.'
          )
      }
  
      // ensure runtime directive metadata
      return true
  }
  
  function genCheckboxModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
  ) {
      const number = modifiers && modifiers.number
      const valueBinding = getBindingAttr(el, 'value') || 'null'
      const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
      const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
      addProp(el, 'checked',
          `Array.isArray(${value})` +
          `?_i(${value},${valueBinding})>-1` + (
              trueValueBinding === 'true'
                  ? `:(${value})`
                  : `:_q(${value},${trueValueBinding})`
          )
      )
      addHandler(el, 'change',
          `var $$a=${value},` +
              '$$el=$event.target,' +
              `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
          'if(Array.isArray($$a)){' +
              `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
                  '$$i=_i($$a,$$v);' +
              `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` +
              `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` +
            `}else{${genAssignmentCode(value, '$$c')}}`,
          null, true
      )
  }
  
  function genRadioModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
  ) {
      const number = modifiers && modifiers.number
      let valueBinding = getBindingAttr(el, 'value') || 'null'
      valueBinding = number ? `_n(${valueBinding})` : valueBinding
      addProp(el, 'checked', `_q(${value},${valueBinding})`)
      addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
  }
  
  function genSelect (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
  ) {
      const number = modifiers && modifiers.number
      const selectedVal = `Array.prototype.filter` +
          `.call($event.target.options,function(o){return o.selected})` +
          `.map(function(o){var val = "_value" in o ? o._value : o.value;` +
          `return ${number ? '_n(val)' : 'val'}})`
  
      const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
      let code = `var $$selectedVal = ${selectedVal};`
      code = `${code} ${genAssignmentCode(value, assignment)}`
      addHandler(el, 'change', code, null, true)
  }
  
  function genDefaultModel (
      el: ASTElement,
      value: string,
      modifiers: ?ASTModifiers
  ): ?boolean {
      const type = el.attrsMap.type
  
      // warn if v-bind:value conflicts with v-model
      // except for inputs with v-bind:type
      if (process.env.NODE_ENV !== 'production') {
          const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
          const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
          if (value && !typeBinding) {
              const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
              warn(
                  `${binding}="${value}" conflicts with v-model on the same element ` +
                  'because the latter already expands to a value binding internally'
              )
          }
      }
  
      const { lazy, number, trim } = modifiers || {}
      const needCompositionGuard = !lazy && type !== 'range'
      const event = lazy
          ? 'change'
          : type === 'range'
              ? RANGE_TOKEN
              : 'input'
  
      let valueExpression = '$event.target.value'
      if (trim) {
          valueExpression = `$event.target.value.trim()`
      }
      if (number) {
          valueExpression = `_n(${valueExpression})`
      }
  
      let code = genAssignmentCode(value, valueExpression)
      if (needCompositionGuard) {
          code = `if($event.target.composing)return;${code}`
      }
  
      addProp(el, 'value', `(${value})`)
      addHandler(el, event, code, null, true)
      if (trim || number) {
          addHandler(el, 'blur', '$forceUpdate()')
      }
  }
  