subframe7536/maple-font · error · TypeError

Invalid item to flatten: {item}

Error message

Invalid item to flatten: {item}

What it means

flatten_to_lines() normalizes feature content into a flat list of Line objects; it accepts items that expose .state(), Line instances, and Lookup/Feature objects (expanded via their .state()). Any other object type in the content list is unsupported and raises TypeError 'Invalid item to flatten'. It guards the internal content model of features, lookups, and lines.

Source

Thrown at source/py/feature/ast.py:559

        yield data


def flatten_to_lines(
    data: Line | Clazz | Lookup | Feature | list | tuple,
) -> list[Line]:
    result = []

    for item in recursive_iterate(data):
        if not item:
            continue
        elif isinstance(item, Clazz):
            result.append(item.state())
        elif isinstance(item, Line):
            result.append(item)
        elif isinstance(item, (Lookup, Feature)):
            result.extend(item.state())
        else:
            raise TypeError(f"Invalid item to flatten: {item}")

    return result


EMPTY_FEAT_CONTENT = [Line("# Placeholder"), subst(None, "EMquad", None, "space")]


def clone_empty(feature: FeatureWithDocs, desc_prefix: str = ""):
    if isinstance(feature, CharacterVariant):
        return CharacterVariant(
            id=feature.id,
            desc=desc_prefix + EMPTY_FEAT_SYMBOL + feature.desc,
            content=EMPTY_FEAT_CONTENT,
            version=feature.version,
            example=feature.example,
        )
    if isinstance(feature, StylisticSet):
        return StylisticSet(

View on GitHub (pinned to c08fda97fe)

Solutions

  1. Wrap plain text in Line(): content = [Line('# my comment'), ...].
  2. Wrap substitution rules in ast.Lookup(...) or use ast.subst()/ast.subst_liga() helpers that return supported objects.
  3. Ensure custom content classes implement .state() returning Line(s).
  4. Flatten nested lists yourself before assigning content (list comprehension over sublists).

Example fix

// before
Feature(id=1, desc='...', content=['# comment', subst(...)], ...)
// after
Feature(id=1, desc='...', content=[Line('# comment'), subst(...)], ...)
Defensive patterns

Strategy: validation

Validate before calling

def validate_content(content):
    for item in content:
        if not (isinstance(item, (ast.Line, ast.Lookup, ast.Feature)) or hasattr(item, 'state')):
            raise TypeError(f'{item!r} must be Line, Lookup, Feature, or expose .state()')

validate_content(my_content)
feature = FeatureWithDocs(id=1, desc='d', content=my_content, version='1.0', example='e')

Type guard

def is_valid_content_item(item) -> bool:
    return isinstance(item, (ast.Line, ast.Lookup, ast.Feature)) or callable(getattr(item, 'state', None))

Try / catch

try:
    lines = feature.state()
except TypeError as e:
    if 'Invalid item to flatten' in str(e):
        bad = str(e).split(': ', 1)[1]
        logging.error(f'replace {bad!r} with Line()/Lookup() wrapper')
    else:
        raise

Prevention

When it happens

Trigger: Passing a feature content list containing raw strings, dicts, integers, or arbitrary objects to anything that flows through flatten_to_lines — Feature/FeatureWithDocs construction content, Lookup, or calls to create/state/cls_states.

Common situations: Hand-writing content as plain strings like '# comment' instead of Line('# comment'); forgetting to wrap substitutions in a Lookup; passing a tuple instead of list; mixing in output of another library.

Related errors


AI-assisted analysis of subframe7536/maple-font@c08fda97fe (2026-08-28). Data as JSON: /api/errors/51477afad8ef470f. Report an issue: GitHub.