{"record":{"id":"b1504ba487e15756","repo":"nodejs/node","slug":"unsupported-type-when-printing-to-gn","errorCode":null,"errorMessage":"Unsupported type when printing to GN.","messagePattern":"Unsupported type when printing to GN\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/gypi_to_gn.py","lineNumber":160,"sourceCode":"      yield ']'\n\n    elif isinstance(v, dict):\n      if level > 0:\n        yield '{'\n      for key in sorted(v):\n        if not isinstance(key, str):\n          raise ValueError('Dictionary key is not a string.')\n        if not key or key[0].isdigit() or not key.replace('_', '').isalnum():\n          raise ValueError('Dictionary key is not a valid GN identifier.')\n        yield key  # No quotations.\n        yield '='\n        for tok in GenerateTokens(v[key], level + 1):\n          yield tok\n      if level > 0:\n        yield '}'\n\n    else:  # Not supporting float: Add only when needed.\n      raise ValueError('Unsupported type when printing to GN.')\n\n  can_start = lambda tok: tok and tok not in ',}]='\n  can_end = lambda tok: tok and tok not in ',{[='\n\n  # Adds whitespaces, trying to keep everything (except dicts) in 1 line.\n  def PlainGlue(gen):\n    prev_tok = None\n    for i, tok in enumerate(gen):\n      if i > 0:\n        if can_end(prev_tok) and can_start(tok):\n          yield '\\n'  # New dict item.\n        elif prev_tok == '[' and tok == ']':\n          yield '  '  # Special case for [].\n        elif tok != ',':\n          yield ' '\n      yield tok\n      prev_tok = tok\n","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gypi_to_gn.py#L142-L178","documentation":"ValueError raised by GenerateTokens for any value type it does not support: only dicts, lists, strings, ints, and bools are handled; the else branch (which the comment notes also excludes float) raises this. It is the catch-all for values the GN printer cannot serialize.","triggerScenarios":"Passing a structure containing a float, None, tuple, set, custom object, or any non-primitive to GenerateTokens.","commonSituations":"GYP data containing floating-point values (e.g. version numbers as floats), None values from optional fields, or tuples introduced by preprocessing.","solutions":["Convert unsupported values to supported types before generation: floats to strings or ints, None to an empty string or omit, tuples to lists.","Extend the handling chain (str, int, bool, list, dict) explicitly for the new type if floats are genuinely needed (the comment invites this).","Validate the data structure recursively and reject/transform unsupported types up front."],"exampleFix":"# before\ndata = {'threshold': 0.5}  # float -> raises\n\n# after\ndata = {'threshold': '0.5'}  # string is supported","handlingStrategy":"type-guard","validationCode":"ALLOWED = (dict, list, str, int, bool)\ndef is_supported_value(obj) -> bool:\n    if isinstance(obj, (str, int, bool)):\n        return True\n    if isinstance(obj, dict):\n        return all(is_supported_value(v) for v in obj.values())\n    if isinstance(obj, list):\n        return all(is_supported_value(i) for i in obj)\n    return False","typeGuard":"def is_gn_primitive(v) -> bool:\n    return isinstance(v, (str, int, bool, list, dict))","tryCatchPattern":"try:\n    list(GenerateTokens(data))\nexcept ValueError as e:\n    if 'Unsupported type' in str(e):\n        pass  # convert floats/None/tuples to supported types, retry","preventionTips":["Convert floats to str/int and None to '' before conversion.","Recursive-walk the data to verify only supported types remain.","Extend the handler chain if a new primitive type is genuinely needed."],"tags":["gyp","gn","build","types","serialization"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}