diff --git a/datatype.go b/datatype.go index 80e1ede..5d2cc13 100644 --- a/datatype.go +++ b/datatype.go @@ -82,8 +82,14 @@ func checkColumns(s nodeScope) error { } } } - if _, err := columnDatatype(column); err != nil { - return fmt.Errorf("%s: field %q: %w", path, name, err) + if len(items) == 0 { + return nil + } + first := itemDatatype(items[0]) + for _, it := range items[1:] { + if d := itemDatatype(it); d != first { + return fmt.Errorf("%s: field %q: %w", path, name, disagreement(items[0], first, it, d)) + } } return nil } @@ -101,41 +107,22 @@ func checkColumns(s nodeScope) error { }) } -// columnDatatype is the datatype a column's items hold. They must agree, since a -// column holds one; a column only ever null is a string. -func columnDatatype(n node) (DataType, error) { +// columnDatatype is the datatype a column holds, which its first item decides: checkColumns refuses +// a column whose items disagree. A table's column, like one only ever null, has no item and is a string. +func columnDatatype(n node) DataType { items, _ := columnItems(n) if len(items) == 0 { - return DataTypeString, nil + return DataTypeString } - first := itemDatatype(items[0]) - for _, t := range items[1:] { - if d := itemDatatype(t); d != first { - return first, disagreement(items[0], first, t, d) - } - } - return first, nil + return itemDatatype(items[0]) } -// itemDatatype is the datatype a column item declares, else that of the column it is. +// itemDatatype is the datatype a column item declares, else that of the column it reads. func itemDatatype(t *template) DataType { - if t.datatype != DataTypeString { - return t.datatype + if t.datatype == DataTypeString && t.readsColumn != nil { + return columnDatatype(t.readsColumn.column) } - return readDatatype(t) -} - -// readDatatype is the datatype of the column t is, reading it by one reference alone; a string -// when t reads none. -func readDatatype(t *template) DataType { - if t.readsColumn == nil { - return DataTypeString - } - items, _ := columnItems(t.readsColumn.column) - if len(items) == 0 { - return DataTypeString - } - return itemDatatype(items[0]) // checkColumns refuses that column where its items disagree + return t.datatype } // columnItems is a column's template items, its choices unwrapped, and whether one is null. diff --git a/record.go b/record.go index f8016bd..67ef410 100644 --- a/record.go +++ b/record.go @@ -254,8 +254,7 @@ func recordOf(n node) (*template, []Column, error) { } columns := make([]Column, len(names)) for i, name := range names { - datatype, _ := columnDatatype(t.fields[name]) // checkColumns refused items that disagree wherever DataType is read - columns[i] = Column{Name: name, DataType: datatype} + columns[i] = Column{Name: name, DataType: columnDatatype(t.fields[name])} } return t, columns, nil } diff --git a/todo.md b/todo.md index 0a08c1e..ce50901 100644 --- a/todo.md +++ b/todo.md @@ -21,8 +21,6 @@ Self-sufficiency 5.6), every seat capped by Locality, and all nine naming This round, in order: -- Derive a column's datatype in one place: `columnItems` yields nothing for a - `tableColumn`, and six callers re-derive it from items. - Give `head`, `index`, `bound` and `draw` one meaning each, name `table.family` for the root it returns, and replace the token kind bytes `'l'`, `'t'`, `'f'` and `'b'`, whose `'l'` means different things in `ftoken` and `formatToken`. diff --git a/value.go b/value.go index 32718e4..89de4d8 100644 --- a/value.go +++ b/value.go @@ -34,8 +34,8 @@ func (p *valueProof) checkDatatype(path string, n node) error { if !ok || t.datatype == DataTypeString { return nil } - if d := readDatatype(t); d == t.datatype { - return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, d) + if r := t.readsColumn; r != nil && columnDatatype(r.column) == t.datatype { + return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, t.datatype) } if reason := p.proveColumnItem(t).not[t.datatype]; reason != "" { return fmt.Errorf("%s: datatype %s: %s", path, t.datatype, reason)