d2lang/d2 · error

failed to unmarshal Position from %q: expected three fields

Error message

failed to unmarshal Position from %q: expected three fields

What it means

Position.UnmarshalText splits the input on ':' and requires exactly three fields: line, column, and byte offset. This error means the position string did not have exactly three colon-separated fields. It surfaces through MakeRange when deserializing positions inside a Range.

Source

Thrown at d2ast/d2ast.go:200

	return fmt.Sprintf("%d:%d", p.Line+1, p.Column+1)
}

func (p Position) Debug() string {
	return fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)
}

// See docs on Range.
func (p Position) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)), nil
}

// See docs on Range.
func (p *Position) UnmarshalText(b []byte) (err error) {
	defer xdefer.Errorf(&err, "failed to unmarshal Position from %q", b)

	fields := bytes.Split(b, []byte{':'})
	if len(fields) != 3 {
		return errors.New("expected three fields")
	}

	p.Line, err = strconv.Atoi(string(fields[0]))
	if err != nil {
		return err
	}
	p.Column, err = strconv.Atoi(string(fields[1]))
	if err != nil {
		return err
	}
	p.Byte, err = strconv.Atoi(string(fields[2]))
	return err
}

// From copies src into p. It's used in the d2parser package to set a node's Range.End to
// the parser's current pos on all return paths with defer.
func (p *Position) From(src *Position) {
	*p = *src

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Provide all three colon-separated fields "line:col:byte", e.g. "1:0:0".
  2. If you only have line:col, compute the byte offset and include it as the third field.
  3. Use Position.MarshalText output as the source of valid position strings.

Example fix

// before
p.UnmarshalText("1:0")
// after
p.UnmarshalText("1:0:0")
Defensive patterns

Strategy: validation

Validate before calling

func validPositionText(s string) bool { return len(strings.Split(s, ":")) == 3 }
if !validPositionText(pos) { return fmt.Errorf("position must be line:col:byte, got %q", pos) }

Type guard

func isPositionText(s string) bool { return len(strings.Split(s, ":")) == 3 }

Try / catch

var p d2ast.Position
if err := p.UnmarshalText(input); err != nil {
    return fmt.Errorf("bad position %q (need line:col:byte): %w", input, err)
}

Prevention

When it happens

Trigger: Calling Position.UnmarshalText (directly or via Range/MakeRange) with strings like "1:0" (two fields) or "1" (one field) instead of "1:0:0".

Common situations: Constructing positions from line:column pairs (only two fields) without the byte-offset third field, or copying a position from a different tool with a different format.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/4feba30077a703ef. Report an issue: GitHub.