Repository: ariga/atlas
Pull request #1231 (2022-10-26 07:42:51)

schemahcl: replace schemahcl.Value with cty.Value


Patch:
diff --git a/cmd/atlas/internal/cmdapi/project.go b/cmd/atlas/internal/cmdapi/project.go
index 2bab1ee4..91eb0c04 100644
--- a/cmd/atlas/internal/cmdapi/project.go
+++ b/cmd/atlas/internal/cmdapi/project.go
@@ -187,9 +187,6 @@ func (e *Env) asMap() (map[string]string, error) {
 			m[attr.K] = v
 			continue
 		}
-		if lv, ok := attr.V.(*schemahcl.LiteralValue); ok {
-			m[attr.K] = lv.V
-		}
 		return nil, fmt.Errorf("expecting attr %q to be a literal, got: %T", attr.K, attr.V)
 	}
 	return m, nil
diff --git a/schemahcl/context.go b/schemahcl/context.go
index 5ef2da3b..841583d8 100644
--- a/schemahcl/context.go
+++ b/schemahcl/context.go
@@ -234,7 +234,7 @@ func blockVars(blocks hclsyntax.Blocks, parentAddr string, defs *blockDef) (map[
 			// Fill missing attributes with zero values.
 			for n := range def.fields {
 				if _, ok := attrs[n]; !ok {
-					attrs[n] = cty.NullVal(ctySchemaLit)
+					attrs[n] = cty.NullVal(ctyNilType)
 				}
 			}
 			self := addr(parentAddr, name, blkName, qualifier)
@@ -308,51 +308,27 @@ func attrMap(attrs hclsyntax.Attributes) map[string]cty.Value {
 		if diag.HasErrors() {
 			continue
 		}
-		literalValue, err := extractValue(value)
-		if err != nil {
-			continue
-		}
-		out[v.Name] = cty.CapsuleVal(ctySchemaLit, literalValue)
+		out[v.Name] = value
 	}
 	return out
 }
 
-// ctySchemaLit is a cty.Capsule type the encapsulates a schemahcl.LiteralValue.
 var (
-	ctySchemaLit = cty.CapsuleWithOps("lit", reflect.TypeOf(LiteralValue{}), &cty.CapsuleOps{
-		// ConversionFrom facilitates reading the encapsulated type as a string, as is needed, for example,
-		// when interpolating it in a string expression.
-		ConversionFrom: func(src cty.Type) func(any, cty.Path) (cty.Value, error) {
-			if src != cty.String {
-				return nil
-			}
-			return func(i any, path cty.Path) (cty.Value, error) {
-				lit, ok := i.(*LiteralValue)
-				if !ok {
-					return cty.Value{}, fmt.Errorf("schemahcl: expected *schemahcl.LiteralValue got %T", i)
-				}
-				uq, err := strconv.Unquote(lit.V)
-				if err != nil {
-					return cty.StringVal(lit.V), nil
-				}
-				return cty.StringVal(uq), nil
-			}
-		},
-	})
 	ctyNilType  = cty.Capsule("type", reflect.TypeOf(cty.NilType))
 	ctyTypeSpec = cty.Capsule("type", reflect.TypeOf(Type{}))
-	ctyRawExpr  = cty.Capsule("raw_expr", reflect.TypeOf(RawExpr{}))
+	ctyRefType  = cty.Capsule("ref", reflect.TypeOf(Ref{}))
+	ctyRawExpr  = cty.Capsule("raw", reflect.TypeOf(RawExpr{}))
 )
 
 // Built-in blocks.
 const (
-	varBlock     = "variable"
-	dataBlock    = "data"
-	localsBlock  = "locals"
-	varRef       = "var"
-	localRef     = "local"
-	dynamicBlock = "dynamic"
-	forEachAttr  = "for_each"
+	varBlock    = "variable"
+	dataBlock   = "data"
+	localsBlock = "locals"
+	forEachAttr = "for_each"
+	eachRef     = "each"
+	varRef      = "var"
+	localRef    = "local"
 )
 
 // defRegistry returns a tree of blockDef structs representing the schema of the
@@ -400,7 +376,7 @@ func (t *blockDef) child(c *blockDef) {
 func (t *blockDef) asCty() cty.Type {
 	f := make(map[string]cty.Type)
 	for attr := range t.fields {
-		f[attr] = ctySchemaLit
+		f[attr] = ctyNilType
 	}
 	f["__ref"] = cty.String
 	for _, c := range t.children {
diff --git a/schemahcl/extension.go b/schemahcl/extension.go
index 04b9acb6..4b71c361 100644
--- a/schemahcl/extension.go
+++ b/schemahcl/extension.go
@@ -8,9 +8,11 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
-	"strconv"
 	"strings"
 	"sync"
+
+	"github.com/zclconf/go-cty/cty"
+	"github.com/zclconf/go-cty/cty/gocty"
 )
 
 // Remainer is the interface that is implemented by types that can store
@@ -318,12 +320,14 @@ func setField(field reflect.Value, attr *Attr) error {
 	case reflect.Interface:
 		field.Set(reflect.ValueOf(attr.V))
 	default:
-		return fmt.Errorf("schemahcl: unsupported field kind %q", field.Kind())
+		if err := gocty.FromCtyValue(attr.V, field.Addr().Interface()); err != nil {
+			return fmt.Errorf("schemahcl: failed setting field %q of type %T: %w", attr.K, field, err)
+		}
 	}
 	return nil
 }
 
-func setPtr(field reflect.Value, val Value) error {
+func setPtr(field reflect.Value, val cty.Value) error {
 	rt := reflect.TypeOf(val)
 	if field.Type() == rt {
 		field.Set(reflect.ValueOf(val))
@@ -331,7 +335,7 @@ func setPtr(field reflect.Value, val Value) error {
 	}
 	// If we are setting a Type field handle RawExpr and Ref specifically.
 	if _, ok := field.Interface().(*Type); ok {
-		switch t := val.(type) {
+		switch t := val.EncapsulatedValue().(type) {
 		case *RawExpr:
 			field.Set(reflect.ValueOf(&Type{T: t.X}))
 			return nil
@@ -347,32 +351,21 @@ func setPtr(field reflect.Value, val Value) error {
 		field.Set(reflect.New(field.Type().Elem()))
 	}
 	switch e := field.Interface().(type) {
-	case *bool:
-		b, err := BoolVal(val)
-		if err != nil {
-			return err
-		}
-		*e = b
-	case *string:
-		s, err := StrVal(val)
-		if err != nil {
-			return err
-		}
-		*e = s
-	case *LiteralValue:
-		s, err := StrVal(val)
-		if err != nil {
-			return err
-		}
-		e.V = s
 	case *Ref:
-		s, err := StrVal(val)
-		if err != nil {
-			return err
+		switch {
+		case val.Type() == cty.String:
+			e.V = val.AsString()
+		case val.Type().IsCapsuleType():
+			ref, ok := val.EncapsulatedValue().(*Ref)
+			if !ok {
+				return fmt.Errorf("schemahcl: expected value to be a *Ref, got: %T", val.EncapsulatedValue())
+			}
+			e.V = ref.V
 		}
-		e.V = s
 	default:
-		return fmt.Errorf("unhandled pointer type %T", val)
+		if err := gocty.FromCtyValue(val, e); err != nil {
+			return fmt.Errorf("converting cty.Value to %T: %w", e, err)
+		}
 	}
 	return nil
 }
@@ -380,18 +373,16 @@ func setPtr(field reflect.Value, val Value) error {
 // setSliceAttr sets the value of attr to the slice field. This function expects both the target field
 // and the source attr to be slices.
 func setSliceAttr(field reflect.Value, attr *Attr) error {
-	lst, ok := attr.V.(*ListValue)
-	if !ok {
-		return fmt.Errorf("schemahcl: field is of type slice but attr %q does not contain a ListValue", attr.K)
+	if !attr.V.Type().IsListType() && !attr.V.Type().IsTupleType() {
+		return fmt.Errorf("schemahcl: field is of type slice but attr %q is type: %s", attr.K, attr.V.Type().FriendlyName())
 	}
 	typ := field.Type().Elem()
-
-	slc := reflect.MakeSlice(reflect.SliceOf(typ), 0, len(lst.V))
+	slc := reflect.MakeSlice(reflect.SliceOf(typ), 0, attr.V.LengthInt())
 	switch typ.Kind() {
 	case reflect.String:
 		s, err := attr.Strings()
 		if err != nil {
-			return fmt.Errorf("cannot read attribute %q as string list: %w", attr.K, err)
+			return fmt.Errorf("cannot read attribute %q of type %q as string list: %w", attr.K, attr.V.Type().FriendlyName(), err)
 		}
 		for _, item := range s {
 			slc = reflect.Append(slc, reflect.ValueOf(item))
@@ -408,8 +399,15 @@ func setSliceAttr(field reflect.Value, attr *Attr) error {
 		if typ != reflect.TypeOf(&Ref{}) {
 			return fmt.Errorf("only pointers to refs supported, got %s", typ)
 		}
-		for _, c := range lst.V {
-			slc = reflect.Append(slc, reflect.ValueOf(c))
+		for _, v := range attr.V.AsValueSlice() {
+			switch {
+			case v.Type().IsCapsuleType():
+				slc = reflect.Append(slc, reflect.ValueOf(v.EncapsulatedValue().(*Ref)))
+			case isRef(v):
+				slc = reflect.Append(slc, reflect.ValueOf(&Ref{V: v.GetAttr("__ref").AsString()}))
+			default:
+				return fmt.Errorf("schemahcl: unsupported type %s in slice", v.Type().FriendlyName())
+			}
 		}
 	default:
 		return fmt.Errorf("slice of unsupported kind: %q", typ.Kind())
@@ -488,90 +486,61 @@ func (r *Resource) Scan(ext any) error {
 func scanPtr(key string, r *Resource, field reflect.Value) error {
 	attr := &Attr{K: key}
 	switch e := field.Interface().(type) {
-	case *LiteralValue:
-		attr.V = e
 	case *Ref:
-		attr.V = e
+		attr.V = cty.CapsuleVal(ctyRefType, e)
 	case *Type:
-		attr.V = e
-	case *bool:
-		attr.V = &LiteralValue{V: strconv.FormatBool(*e)}
-	case *string:
-		attr.V = &LiteralValue{V: strconv.Quote(*e)}
+		attr.V = cty.CapsuleVal(ctyTypeSpec, e)
 	default:
-		return fmt.Errorf("schemahcl: unsupported pointer to %s", e)
+		t, err := gocty.ImpliedType(e)
+		if err != nil {
+			return fmt.Errorf("schemahcl: cannot infer type for field %q when scaning pointer: %w", key, err)
+		}
+		attr.V, err = gocty.ToCtyValue(e, t)
+		if err != nil {
+			return fmt.Errorf("schemahcl: cannot convert value for field %q: %w", key, err)
+		}
 	}
 	r.SetAttr(attr)
 	return nil
 }
 
 func scanAttr(key string, r *Resource, field reflect.Value) error {
-	var lit string
-	switch field.Kind() {
-	case reflect.Slice:
-		return scanSliceAttr(key, r, field)
-	case reflect.String:
-		lit = strconv.Quote(field.String())
-	case reflect.Int:
-		lit = fmt.Sprintf("%d", field.Int())
-	case reflect.Bool:
-		lit = strconv.FormatBool(field.Bool())
-	case reflect.Interface:
+	switch k := field.Kind(); {
+	case k == reflect.Interface:
 		if field.IsNil() {
-			return nil
+			break
 		}
 		i := field.Interface()
-		v, ok := i.(Value)
+		v, ok := i.(cty.Value)
 		if !ok {
 			return fmt.Errorf("schemahcl: unsupported interface type %T for field %q", i, key)
 		}
-		r.SetAttr(&Attr{
-			K: key,
-			V: v,
-		})
-		return nil
-	default:
-		return fmt.Errorf("schemahcl: unsupported field kind %q", field.Kind())
-	}
-	r.SetAttr(&Attr{
-		K: key,
-		V: &LiteralValue{V: lit},
-	})
-	return nil
-}
-
-// scanSliceAttr sets an Attr named "key" into the Resource r, by converting
-// the value stored in "field" into a *ListValue.
-func scanSliceAttr(key string, r *Resource, field reflect.Value) error {
-	typ := field.Type()
-	lst := &ListValue{}
-
-	switch typ.Elem().Kind() {
-	case reflect.String:
-		for i := 0; i < field.Len(); i++ {
-			item := field.Index(i).Interface().(string)
-			lst.V = append(lst.V, &LiteralValue{V: strconv.Quote(item)})
+		r.SetAttr(&Attr{K: key, V: v})
+	case field.Type() == reflect.TypeOf([]*Ref{}):
+		if field.Len() > 0 {
+			r.SetAttr(RefsAttr(key, field.Interface().([]*Ref)...))
 		}
-	case reflect.Bool:
-		for i := 0; i < field.Len(); i++ {
-			item := field.Index(i).Interface().(bool)
-			lst.V = append(lst.V, &LiteralValue{V: strconv.FormatBool(item)})
+	case k == reflect.Int, k == reflect.Int64:
+		r.SetAttr(Int64Attr(key, field.Int()))
+	case k == reflect.Struct:
+		if v, ok := field.Interface().(cty.Value); ok && v.IsNull() {
+			break
 		}
-	case reflect.Ptr:
-		if typ.Elem() != reflect.TypeOf(&Ref{}) {
-			return fmt.Errorf("schemahcl: currently on ref slice values supported, got %s", typ)
+		fallthrough
+	default:
+		t, err := gocty.ImpliedType(field.Interface())
+		if err != nil {
+			return fmt.Errorf("schemahcl: cannot infer type for field %q when scanning attribute: %w", key, err)
 		}
-		for i := 0; i < field.Len(); i++ {
-			item := field.Index(i).Interface().(*Ref)
-			lst.V = append(lst.V, item)
+		v, err := gocty.ToCtyValue(field.Interface(), t)
+		if err != nil {
+			return fmt.Errorf("schemahcl: cannot convert value for field %q: %w", key, err)
 		}
-	default:
-		return fmt.Errorf("unsupported kind %q for %q", typ.Kind(), key)
+		r.SetAttr(&Attr{
+			K: key,
+			V: v,
+		})
 	}
-	r.SetAttr(&Attr{
-		K: key,
-		V: lst,
-	})
 	return nil
 }
 
diff --git a/schemahcl/hcl.go b/schemahcl/hcl.go
index fe66c029..d5f9059f 100644
--- a/schemahcl/hcl.go
+++ b/schemahcl/hcl.go
@@ -6,15 +6,13 @@ package schemahcl
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
+	"reflect"
 	"sort"
 	"strconv"
 	"strings"
 
 	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/ext/dynblock"
-	"github.com/hashicorp/hcl/v2/hcldec"
 	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/hashicorp/hcl/v2/hclsyntax"
 	"github.com/hashicorp/hcl/v2/hclwrite"
@@ -151,14 +149,16 @@ func patchRefs(spec *Resource) error {
 func (r addrRef) patch(resource *Resource) error {
 	cp := r.copy().load(resource, "")
 	for _, attr := range resource.Attrs {
-		if ref, ok := attr.V.(*Ref); ok {
-			referenced, ok := cp[ref.V]
-			if !ok {
-				return fmt.Errorf("broken reference to %q", ref.V)
-			}
-			if name, err := referenced.FinalName(); err == nil {
-				ref.V = strings.ReplaceAll(ref.V, referenced.Name, name)
-			}
+		if !attr.IsRef() {
+			continue
+		}
+		ref := attr.V.EncapsulatedValue().(*Ref)
+		referenced, ok := cp[ref.V]
+		if !ok {
+			return fmt.Errorf("broken reference to %q", ref.V)
+		}
+		if name, err := referenced.FinalName(); err == nil {
+			ref.V = strings.ReplaceAll(ref.V, referenced.Name, name)
 		}
 	}
 	for _, ch := range resource.Children {
@@ -238,7 +238,7 @@ func (s *State) resource(ctx *hcl.EvalContext, file *hcl.File) (*Resource, error
 			// Iterate over the set and create a resource for each element.
 			for it := forEach.ElementIterator(); it.Next(); {
 				k, v := it.Element()
-				ctx.Variables["each"] = cty.ObjectVal(map[string]cty.Value{"key": k, "value": v})
+				ctx.Variables[eachRef] = cty.ObjectVal(map[string]cty.Value{"key": k, "value": v})
 				resource, err := s.toResource(ctx, blk, []string{blk.Type})
 				if err != nil {
 					return nil, err
@@ -277,21 +277,21 @@ func (s *State) toAttrs(ctx *hcl.EvalContext, hclAttrs hclsyntax.Attributes, sco
 		if diag.HasErrors() {
 			return nil, s.typeError(diag)
 		}
-		var err error
-		switch {
+		switch t := value.Type(); {
 		case isRef(value):
-			at.V = &Ref{V: value.GetAttr("__ref").AsString()}
-		case value.Type() == ctyRawExpr:
-			at.V = value.EncapsulatedValue().(*RawExpr)
-		case value.Type() == ctyTypeSpec:
-			at.V = value.EncapsulatedValue().(*Type)
-		case value.Type().IsTupleType():
-			at.V, err = extractListValue(value)
+			at.V = cty.CapsuleVal(ctyRefType, &Ref{V: value.GetAttr("__ref").AsString()})
+		case (t.IsTupleType() || t.IsListType() || t.IsSetType()) && value.LengthInt() > 0:
+			values := make([]cty.Value, 0, value.LengthInt())
+			for it := value.ElementIterator(); it.Next(); {
+				_, v := it.Element()
+				if isRef(v) {
+					v = cty.CapsuleVal(ctyRefType, &Ref{V: v.GetAttr("__ref").AsString()})
+				}
+				values = append(values, v)
+			}
+			at.V = cty.ListVal(values)
 		default:
-			at.V, err = extractValue(value)
-		}
-		if err != nil {
-			return nil, err
+			at.V = value
 		}
 		attrs = append(attrs, at)
 	}
@@ -329,53 +329,6 @@ func isRef(v cty.Value) bool {
 	return v.Type().IsObjectType() && v.Type().HasAttribute("__ref")
 }
 
-func extractListValue(value cty.Value) (*ListValue, error) {
-	lst := &ListValue{}
-	it := value.ElementIterator()
-	for it.Next() {
-		_, v := it.Element()
-		if isRef(v) {
-			lst.V = append(lst.V, &Ref{V: v.GetAttr("__ref").AsString()})
-			continue
-		}
-		litv, err := extractValue(v)
-		if err != nil {
-			return nil, err
-		}
-		lst.V = append(lst.V, litv)
-	}
-	return lst, nil
-}
-
-func extractValue(value cty.Value) (Value, error) {
-	switch value.Type() {
-	case ctySchemaLit:
-		return value.EncapsulatedValue().(*LiteralValue), nil
-	case cty.String:
-		return &LiteralValue{V: strconv.Quote(value.AsString())}, nil
-	case cty.Number:
-		bf := value.AsBigFloat()
-		num, _ := bf.Float64()
-		return &LiteralValue{V: strconv.FormatFloat(num, 'f', -1, 64)}, nil
-	case cty.Bool:
-		return &LiteralValue{V: strconv.FormatBool(value.True())}, nil
-	case cty.List(cty.String), cty.List(cty.Number), cty.List(cty.Bool), cty.List(cty.NilType),
-		cty.Set(cty.String), cty.Set(cty.Number), cty.Set(cty.Bool), cty.Set(cty.NilType):
-		l := &ListValue{V: make([]Value, 0, value.LengthInt())}
-		for it := value.ElementIterator(); it.Next(); {
-			_, v := it.Element()
-			ev, err := extractValue(v)
-			if err != nil {
-				return nil, err
-			}
-			l.V = append(l.V, ev)
-		}
-		return l, nil
-	default:
-		return nil, fmt.Errorf("schemahcl: unsupported type %q", value.Type().GoString())
-	}
-}
-
 func (s *State) toResource(ctx *hcl.EvalContext, block *hclsyntax.Block, scope []string) (*Resource, error) {
 	spec := &Resource{
 		Type: block.Type,
@@ -397,37 +350,11 @@ func (s *State) toResource(ctx *hcl.EvalContext, block *hclsyntax.Block, scope [
 	}
 	spec.Attrs = attrs
 	for _, blk := range block.Body.Blocks {
-		switch blk.Type {
-		case dynamicBlock:
-			dec, err := dynamicSpec(blk)
-			if err != nil {
-				return nil, err
-			}
-			v, _, diag := hcldec.PartialDecode(dynblock.Expand(block.Body, ctx), dec, ctx)
-			if diag.HasErrors() {
-				return nil, diag
-			}
-			for _, e := range v.GetAttr(blk.Labels[0]).AsValueSlice() {
-				r := &Resource{Type: blk.Labels[0]}
-				for k, v := range e.AsValueMap() {
-					av, err := extractValue(v)
-					if err != nil {
-						return nil, err
-					}
-					r.Attrs = append(r.Attrs, &Attr{
-						K: k,
-						V: av,
-					})
-				}
-				spec.Children = append(spec.Children, r)
-			}
-		default:
-			r, err := s.toResource(ctx, blk, append(scope, blk.Type))
-			if err != nil {
-				return nil, err
-			}
-			spec.Children = append(spec.Children, r)
+		r, err := s.toResource(ctx, blk, append(scope, blk.Type))
+		if err != nil {
+			return nil, err
 		}
+		spec.Children = append(spec.Children, r)
 	}
 	return spec, nil
 }
@@ -485,71 +412,66 @@ func labels(r *Resource) []string {
 }
 
 func (s *State) writeAttr(attr *Attr, body *hclwrite.Body) error {
-	attr = normalizeLiterals(attr)
-	switch v := attr.V.(type) {
-	case *Ref:
-		body.SetAttributeRaw(attr.K, hclRefTokens(v.V))
-	case *Type:
-		if v.IsRef {
-			body.SetAttributeRaw(attr.K, hclRefTokens(v.T))
+	switch {
+	case attr.IsRef():
+		v, err := attr.Ref()
+		if err != nil {
+			return err
+		}
+		body.SetAttributeRaw(attr.K, hclRefTokens(v))
+	case attr.IsType():
+		t, err := attr.Type()
+		if err != nil {
+			return err
+		}
+		if t.IsRef {
+			body.SetAttributeRaw(attr.K, hclRefTokens(t.T))
 			break
 		}
-		spec, ok := s.findTypeSpec(v.T)
+		spec, ok := s.findTypeSpec(t.T)
 		if !ok {
-			v := fmt.Sprintf("sql(%q)", v.T)
+			v := fmt.Sprintf("sql(%q)", t.T)
 			body.SetAttributeRaw(attr.K, hclRawTokens(v))
 			break
 		}
-		st, err := hclType(spec, v)
+		st, err := hclType(spec, t)
 		if err != nil {
 			return err
 		}
 		body.SetAttributeRaw(attr.K, hclRawTokens(st))
-	case *LiteralValue:
-		body.SetAttributeRaw(attr.K, hclRawTokens(v.V))
-	case *RawExpr:
+	case attr.IsRawExpr():
+		v, err := attr.RawExpr()
+		if err != nil {
+			return err
+		}
 		// TODO(rotemtam): the func name should be decided on contextual basis.
 		fnc := fmt.Sprintf("sql(%q)", v.X)
 		body.SetAttributeRaw(attr.K, hclRawTokens(fnc))
-	case *ListValue:
+	case attr.V.Type().IsListType():
 		// Skip scanning nil slices ([]T(nil)) by default. Users that
 		// want to print empty lists, should use make([]T, 0) instead.
-		if v.V == nil {
+		if attr.V.LengthInt() == 0 {
 			return nil
 		}
-		lst := make([]hclwrite.Tokens, 0, len(v.V))
-		for _, item := range v.V {
-			switch v := item.(type) {
-			case *Ref:
-				lst = append(lst, hclRefTokens(v.V))
-			case *LiteralValue:
-				lst = append(lst, hclRawTokens(v.V))
-			default:
-				return fmt.Errorf("cannot write elem type %T of attr %q to HCL list", v, attr)
+		tokens := make([]hclwrite.Tokens, 0, attr.V.LengthInt())
+		for _, v := range attr.V.AsValueSlice() {
+			if v.Type().IsCapsuleType() {
+				ref, ok := v.EncapsulatedValue().(*Ref)
+				if !ok {
+					return fmt.Errorf("unsupported capsule type: %v", v.Type())
+				}
+				tokens = append(tokens, hclRefTokens(ref.V))
+			} else {
+				tokens = append(tokens, hclwrite.TokensForValue(v))
 			}
 		}
-		body.SetAttributeRaw(attr.K, hclList(lst))
+		body.SetAttributeRaw(attr.K, hclList(tokens))
 	default:
-		return fmt.Errorf("schemacl: unknown literal type %T", v)
+		body.SetAttributeValue(attr.K, attr.V)
 	}
 	return nil
 }
 
-// normalizeLiterals transforms attributes with LiteralValue that cannot be
-// written as correct HCL into RawExpr.
-func normalizeLiterals(attr *Attr) *Attr {
-	lv, ok := attr.V.(*LiteralValue)
-	if !ok {
-		return attr
-	}
-	exp := "x = " + lv.V
-	p := hclparse.NewParser()
-	if _, diag := p.ParseHCL([]byte(exp), ""); diag != nil {
-		return &Attr{K: attr.K, V: &RawExpr{X: lv.V}}
-	}
-	return attr
-}
-
 func (s *State) findTypeSpec(t string) (*TypeSpec, bool) {
 	for _, v := range s.config.types {
 		if v.T == t {
@@ -572,18 +494,7 @@ func hclType(spec *TypeSpec, typ *Type) (string, error) {
 		if !ok {
 			continue
 		}
-		switch val := arg.V.(type) {
-		case *LiteralValue:
-			args = append(args, val.V)
-		case *ListValue:
-			for _, li := range val.V {
-				lit, ok := li.(*LiteralValue)
-				if !ok {
-					return "", errors.New("expecting literal value")
-				}
-				args = append(args, lit.V)
-			}
-		}
+		args = append(args, valueArgs(param, arg.V)...)
 	}
 	// If no args were chosen and the type can be described without a function.
 	if len(args) == 0 && len(typeFuncReqArgs(spec)) == 0 {
@@ -592,6 +503,28 @@ func hclType(spec *TypeSpec, typ *Type) (string, error) {
 	return fmt.Sprintf("%s(%s)", spec.Name, strings.Join(args, ",")), nil
 }
 
+func valueArgs(spec *TypeAttr, v cty.Value) []string {
+	switch {
+	case v.Type().IsListType(), v.Type().IsTupleType(), v.Type().IsSetType(), v.Type().IsCollectionType():
+		args := make([]string, 0, v.LengthInt())
+		for _, v := range v.AsValueSlice() {
+			args = append(args, valueArgs(spec, v)...)
+		}
+		return args
+	case v.Type() == cty.String:
+		return []string{strconv.Quote(v.AsString())}
+	case v.Type() == cty.Number && spec.Kind == reflect.Int:
+		iv, _ := v.AsBigFloat().Int64()
+		return []string{strconv.FormatInt(iv, 10)}
+	case v.Type() == cty.Number:
+		fv, _ := v.AsBigFloat().Float64()
+		return []string{strconv.FormatFloat(fv, 'f', -1, 64)}
+	case v.Type() == cty.Bool:
+		return []string{strconv.FormatBool(v.True())}
+	}
+	return nil
+}
+
 func findAttr(attrs []*Attr, k string) (*Attr, bool) {
 	for _, attr := range attrs {
 		if attr.K == k {
@@ -665,28 +598,3 @@ func hclList(items []hclwrite.Tokens) hclwrite.Tokens {
 func (f EvalFunc) Eval(p *hclparse.Parser, i any, input map[string]cty.Value) error {
 	return f(p, i, input)
 }
-
-func dynamicSpec(b *hclsyntax.Block) (hcldec.Spec, error) {
-	switch {
-	case len(b.Labels) != 1:
-		return nil, fmt.Errorf("unexpected number of labels for 'dynamic' block: %d", len(b.Labels))
-	case b.Body == nil || len(b.Body.Attributes) != 1 || b.Body.Attributes["for_each"] == nil:
-		return nil, fmt.Errorf("expect a single 'for_each' attribute on 'dynamic' block: %q", b.Labels[0])
-	case len(b.Body.Blocks) != 1 || b.Body.Blocks[0].Type != "content" || b.Body.Blocks[0].Body == nil:
-		return nil, fmt.Errorf("expect a non-empty 'content' block on 'dynamic' block: %q", b.Labels[0])
-	}
-	attrs := make(hcldec.ObjectSpec)
-	for name := range b.Body.Blocks[0].Body.Attributes {
-		attrs[name] = &hcldec.AttrSpec{
-			Name:     name,
-			Required: true,
-			Type:     cty.DynamicPseudoType,
-		}
-	}
-	return &hcldec.ObjectSpec{
-		b.Labels[0]: &hcldec.BlockListSpec{
-			TypeName: b.Labels[0],
-			Nested:   attrs,
-		},
-	}, nil
-}
diff --git a/schemahcl/opts.go b/schemahcl/opts.go
index 42de4f0c..63fae765 100644
--- a/schemahcl/opts.go
+++ b/schemahcl/opts.go
@@ -188,28 +188,14 @@ func typeFuncSpecImpl(_ *function.Spec, typeSpec *TypeSpec) function.ImplFunc {
 		for _, attr := range typeFuncArgs(typeSpec) {
 			// If the attribute is a slice, read all remaining args into a list value.
 			if attr.Kind == reflect.Slice {
-				lst := &ListValue{}
-				for _, arg := range args {
-					v, err := extractValue(arg)
-					if err != nil {
-						return cty.NilVal, err
-					}
-					lst.V = append(lst.V, v)
-				}
-				t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: lst})
+				t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: cty.ListVal(args)})
 				break
 			}
 			if len(args) == 0 {
 				break
 			}
-			// Pop the first arg and add it as a literal to the type.
-			var arg cty.Value
-			arg, args = args[0], args[1:]
-			v, err := extractValue(arg)
-			if err != nil {
-				return cty.NilVal, err
-			}
-			t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: v})
+			t.Attrs = append(t.Attrs, &Attr{K: attr.Name, V: args[0]})
+			args = args[1:]
 		}
 		return cty.CapsuleVal(ctyTypeSpec, t), nil
 	}
diff --git a/schemahcl/spec.go b/schemahcl/spec.go
index f16f4df9..27afd651 100644
--- a/schemahcl/spec.go
+++ b/schemahcl/spec.go
@@ -6,10 +6,13 @@ package schemahcl
 
 import (
 	"fmt"
+	"math/big"
 	"reflect"
-	"strconv"
 
 	"ariga.io/atlas/sql/schema"
+
+	"github.com/zclconf/go-cty/cty"
+	"github.com/zclconf/go-cty/cty/gocty"
 )
 
 type (
@@ -25,27 +28,7 @@ type (
 	// Attr is an attribute of a Resource.
 	Attr struct {
 		K string
-		V Value
-	}
-
-	// Value represents the value of an Attr.
-	Value interface {
-		val()
-	}
-
-	// LiteralValue implements Value and represents a literal value (string, number, etc.)
-	LiteralValue struct {
-		V string
-	}
-
-	// RawExpr implements Value and represents any raw expression.
-	RawExpr struct {
-		X string
-	}
-
-	// ListValue implements Value and represents a list of Values.
-	ListValue struct {
-		V []Value
+		V cty.Value
 	}
 
 	// Ref implements Value and represents a reference to another Resource.
@@ -57,6 +40,11 @@ type (
 		V string
 	}
 
+	// RawExpr implements Value and represents any raw expression.
+	RawExpr struct {
+		X string
+	}
+
 	// TypeSpec represents a specification for defining a Type.
 	TypeSpec struct {
 		// Name is the identifier for the type in an Atlas DDL document.
@@ -103,6 +91,33 @@ type (
 	}
 )
 
+// IsRef indicates if the attribute is a reference type.
+func (a *Attr) IsRef() bool {
+	if !a.V.Type().IsCapsuleType() {
+		return false
+	}
+	_, ok := a.V.EncapsulatedValue().(*Ref)
+	return ok
+}
+
+// IsRawExpr indicates if the attribute is a RawExpr type.
+func (a *Attr) IsRawExpr() bool {
+	if !a.V.Type().IsCapsuleType() {
+		return false
+	}
+	_, ok := a.V.EncapsulatedValue().(*RawExpr)
+	return ok
+}
+
+// IsType indicates if the attribute is a type spec.
+func (a *Attr) IsType() bool {
+	if !a.V.Type().IsCapsuleType() {
+		return false
+	}
+	_, ok := a.V.EncapsulatedValue().(*Type)
+	return ok
+}
+
 // Int returns an int from the Value of the Attr. If The value is not a LiteralValue or the value
 // cannot be converted to an integer an error is returned.
 func (a *Attr) Int() (int, error) {
@@ -115,14 +130,9 @@ func (a *Attr) Int() (int, error) {
 
 // Int64 returns an int64 from the Value of the Attr. If The value is not a LiteralValue or the value
 // cannot be converted to an integer an error is returned.
-func (a *Attr) Int64() (int64, error) {
-	lit, ok := a.V.(*LiteralValue)
-	if !ok {
-		return 0, fmt.Errorf("schema: cannot read attribute %q as literal", a.K)
-	}
-	i, err := strconv.ParseInt(lit.V, 10, 64)
-	if err != nil {
-		return 0, fmt.Errorf("schema: cannot read attribute %q as integer: %w", a.K, err)
+func (a *Attr) Int64() (i int64, err error) {
+	if err = gocty.FromCtyValue(a.V, &i); err != nil {
+		return 0, err
 	}
 	return i, nil
 }
@@ -130,85 +140,97 @@ func (a *Attr) Int64() (int64, error) {
 // String returns a string from the Value of the Attr. If The value is not a LiteralValue
 // an error is returned.  String values are expected to be quoted. If the value is not
 // properly quoted an error is returned.
-func (a *Attr) String() (string, error) {
-	return StrVal(a.V)
+func (a *Attr) String() (s string, err error) {
+	if err = gocty.FromCtyValue(a.V, &s); err != nil {
+		return "", err
+	}
+	return s, nil
 }
 
 // Bool returns a boolean from the Value of the Attr. If The value is not a LiteralValue or the value
 // cannot be converted to a boolean an error is returned.
-func (a *Attr) Bool() (bool, error) {
-	lit, ok := a.V.(*LiteralValue)
-	if !ok {
-		return false, fmt.Errorf("schema: cannot read attribute %q as literal", a.K)
-	}
-	b, err := strconv.ParseBool(lit.V)
-	if err != nil {
-		return false, fmt.Errorf("schema: cannot read attribute %q as bool: %w", a.K, err)
+func (a *Attr) Bool() (b bool, err error) {
+	if err = gocty.FromCtyValue(a.V, &b); err != nil {
+		return false, err
 	}
 	return b, nil
 }
 
-// Ref returns the string representation of the Attr. If the value is not a Ref or the value
-// an error is returned.
+// Ref extracts the reference from the Value of the Attr.
 func (a *Attr) Ref() (string, error) {
-	ref, ok := a.V.(*Ref)
+	ref, ok := a.V.EncapsulatedValue().(*Ref)
 	if !ok {
 		return "", fmt.Errorf("schema: cannot read attribute %q as ref", a.K)
 	}
 	return ref.V, nil
 }
 
-// Refs returns a slice of references.
-func (a *Attr) Refs() ([]*Ref, error) {
-	l, ok := a.V.(*ListValue)
+// Type extracts the Type from the Attr.
+func (a *Attr) Type() (*Type, error) {
+	t, ok := a.V.EncapsulatedValue().(*Type)
 	if !ok {
-		return nil, fmt.Errorf("schema: attribute %q is not a list", a.K)
+		return nil, fmt.Errorf("schema: cannot read attribute %q as type", a.K)
+	}
+	return t, nil
+}
+
+// RawExpr extracts the RawExpr from the Attr.
+func (a *Attr) RawExpr() (*RawExpr, error) {
+	if !a.IsRawExpr() {
+		return nil, fmt.Errorf("schema: cannot read attribute %q as raw expression", a.K)
 	}
-	refs := make([]*Ref, 0, len(l.V))
-	for _, v := range l.V {
-		r, ok := v.(*Ref)
+	return a.V.EncapsulatedValue().(*RawExpr), nil
+}
+
+// Refs returns a slice of references.
+func (a *Attr) Refs() ([]*Ref, error) {
+	refs := make([]*Ref, 0, len(a.V.AsValueSlice()))
+	for _, v := range a.V.AsValueSlice() {
+		ref, ok := v.EncapsulatedValue().(*Ref)
 		if !ok {
-			return nil, fmt.Errorf("schemahcl: expected %T to be Ref", v)
+			return nil, fmt.Errorf("schema: cannot read attribute %q as ref", a.K)
 		}
-		refs = append(refs, r)
+		refs = append(refs, ref)
 	}
 	return refs, nil
 }
 
 // Strings returns a slice of strings from the Value of the Attr. If The value is not a ListValue or its
 // values cannot be converted to strings an error is returned.
-func (a *Attr) Strings() ([]string, error) {
-	l, ok := a.V.(*ListValue)
-	if !ok {
-		return nil, fmt.Errorf("schema: attribute %q is not a list", a.K)
-	}
-	out := make([]string, 0, len(l.V))
-	for _, item := range l.V {
-		sv, err := StrVal(item)
-		if err != nil {
-			return nil, fmt.Errorf("schemahcl: failed parsing item %q to string: %w", item, err)
+func (a *Attr) Strings() (vs []string, err error) {
+	if a.V.Type().IsTupleType() {
+		for _, v := range a.V.AsValueSlice() {
+			var s string
+			if err = gocty.FromCtyValue(v, &s); err != nil {
+				return nil, err
+			}
+			vs = append(vs, s)
 		}
-		out = append(out, sv)
+		return vs, nil
+	}
+	if err = gocty.FromCtyValue(a.V, &vs); err != nil {
+		return nil, err
 	}
-	return out, nil
+	return vs, nil
 }
 
 // Bools returns a slice of bools from the Value of the Attr. If The value is not a ListValue or its
 // values cannot be converted to bools an error is returned.
-func (a *Attr) Bools() ([]bool, error) {
-	lst, ok := a.V.(*ListValue)
-	if !ok {
-		return nil, fmt.Errorf("schemahcl: attribute %q is not a list", a.K)
-	}
-	out := make([]bool, 0, len(lst.V))
-	for _, item := range lst.V {
-		b, err := BoolVal(item)
-		if err != nil {
-			return nil, err
+func (a *Attr) Bools() (vs []bool, err error) {
+	if a.V.Type().IsTupleType() {
+		for _, v := range a.V.AsValueSlice() {
+			var b bool
+			if err = gocty.FromCtyValue(v, &b); err != nil {
+				return nil, err
+			}
+			vs = append(vs, b)
 		}
-		out = append(out, b)
+		return vs, nil
+	}
+	if err = gocty.FromCtyValue(a.V, &vs); err != nil {
+		return nil, err
 	}
-	return out, nil
+	return vs, nil
 }
 
 // Resource returns the first child Resource by its type and reports whether it was found.
@@ -262,32 +284,6 @@ func replaceOrAppendAttr(attrs []*Attr, attr *Attr) []*Attr {
 	return append(attrs, attr)
 }
 
-// StrVal returns the raw string representation of v. If v is not a *LiteralValue
-// it returns an error. If the raw string representation of v cannot be read as
-// a string by unquoting it, an error is returned as well.
-func StrVal(v Value) (string, error) {
-	lit, ok := v.(*LiteralValue)
-	if !ok {
-		return "", fmt.Errorf("schemahcl: expected %T to be LiteralValue", v)
-	}
-	return strconv.Unquote(lit.V)
-}
-
-// BoolVal returns the bool representation of v. If v is not a *LiteralValue
-// it returns an error. If the raw string representation of v cannot be read as
-// a bool, an error is returned as well.
-func BoolVal(v Value) (bool, error) {
-	lit, ok := v.(*LiteralValue)
-	if !ok {
-		return false, fmt.Errorf("schemahcl: expected %T to be LiteralValue", v)
-	}
-	b, err := strconv.ParseBool(lit.V)
-	if err != nil {
-		return false, fmt.Errorf("schemahcl: failed parsing %q as bool: %w", lit.V, err)
-	}
-	return b, nil
-}
-
 // Attr returns a TypeAttr by name and reports if one was found.
 func (s *TypeSpec) Attr(name string) (*TypeAttr, bool) {
 	for _, ta := range s.Attributes {
@@ -298,38 +294,78 @@ func (s *TypeSpec) Attr(name string) (*TypeAttr, bool) {
 	return nil, false
 }
 
-func (*LiteralValue) val() {}
-func (*RawExpr) val()      {}
-func (*ListValue) val()    {}
-func (*Ref) val()          {}
-func (*Type) val()         {}
+var _ Marshaler = MarshalerFunc(nil)
 
-var (
-	_ Marshaler = MarshalerFunc(nil)
-)
+// StringAttr is a helper method for constructing *schemahcl.Attr instances that contain string value.
+func StringAttr(k string, v string) *Attr {
+	return &Attr{
+		K: k,
+		V: cty.StringVal(v),
+	}
+}
+
+// IntAttr is a helper method for constructing *schemahcl.Attr instances that contain int64 value.
+func IntAttr(k string, v int) *Attr {
+	return Int64Attr(k, int64(v))
+}
+
+// Int64Attr is a helper method for constructing *schemahcl.Attr instances that contain int64 value.
+func Int64Attr(k string, v int64) *Attr {
+	return &Attr{
+		K: k,
+		V: cty.NumberVal(new(big.Float).SetInt64(v).SetPrec(512)),
+	}
+}
+
+// BoolAttr is a helper method for constructing *schemahcl.Attr instances that contain a boolean value.
+func BoolAttr(k string, v bool) *Attr {
+	return &Attr{
+		K: k,
+		V: cty.BoolVal(v),
+	}
+}
 
-// LitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values.
-func LitAttr(k, v string) *Attr {
+// RefAttr is a helper method for constructing *schemahcl.Attr instances that contain a Ref value.
+func RefAttr(k string, v *Ref) *Attr {
 	return &Attr{
 		K: k,
-		V: &LiteralValue{V: v},
+		V: cty.CapsuleVal(ctyRefType, v),
 	}
 }
 
-// StrLitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values
-// representing string literals.
-func StrLitAttr(k, v string) *Attr {
-	return LitAttr(k, strconv.Quote(v))
+// StringsAttr is a helper method for constructing *schemahcl.Attr instances that contain list strings.
+func StringsAttr(k string, vs ...string) *Attr {
+	vv := make([]cty.Value, len(vs))
+	for i, v := range vs {
+		vv[i] = cty.StringVal(v)
+	}
+	return &Attr{
+		K: k,
+		V: cty.ListVal(vv),
+	}
 }
 
-// ListAttr is a helper method for constructing *schemahcl.Attr instances that contain list values.
-func ListAttr(k string, litValues ...string) *Attr {
-	lv := &ListValue{}
-	for _, v := range litValues {
-		lv.V = append(lv.V, &LiteralValue{V: v})
+// RefsAttr is a helper method for constructing *schemahcl.Attr instances that contain list references.
+func RefsAttr(k string, refs ...*Ref) *Attr {
+	vv := make([]cty.Value, len(refs))
+	for i, v := range refs {
+		vv[i] = cty.CapsuleVal(ctyRefType, v)
 	}
 	return &Attr{
 		K: k,
-		V: lv,
+		V: cty.ListVal(vv),
 	}
 }
+
+// RawAttr is a helper method for constructing *schemahcl.Attr instances that contain RawExpr value.
+func RawAttr(k string, x string) *Attr {
+	return &Attr{
+		K: k,
+		V: RawExprValue(&RawExpr{X: x}),
+	}
+}
+
+// RawExprValue is a helper method for constructing a cty.Value that capsules a raw expression.
+func RawExprValue(x *RawExpr) cty.Value {
+	return cty.CapsuleVal(ctyRawExpr, x)
+}
diff --git a/schemahcl/types.go b/schemahcl/types.go
index 737fd4bb..c4c5272e 100644
--- a/schemahcl/types.go
+++ b/schemahcl/types.go
@@ -9,7 +9,6 @@ import (
 	"fmt"
 	"log"
 	"reflect"
-	"strconv"
 	"strings"
 
 	"ariga.io/atlas/sql/schema"
@@ -43,24 +42,11 @@ func (r *TypeRegistry) PrintType(typ *Type) (string, error) {
 			}
 			continue
 		}
-		switch v := arg.V.(type) {
-		case *LiteralValue:
-			args = append(args, v.V)
-		case *ListValue:
-			for _, li := range v.V {
-				lit, ok := li.(*LiteralValue)
-				if !ok {
-					return "", fmt.Errorf("expecting literal value. got: %T", li)
-				}
-				uq, err := strconv.Unquote(lit.V)
-				if err != nil {
-					return "", fmt.Errorf("expecting list items to be quoted strings: %w", err)
-				}
-				args = append(args, "'"+uq+"'")
-			}
-		default:
-			return "", fmt.Errorf("unsupported type %T for PrintType", v)
+		attr, ok := spec.Attr(arg.K)
+		if !ok {
+			return "", fmt.Errorf("specutil: attribute %q not found in typespec %q", arg.K, typ.T)
 		}
+		args = append(args, valueArgs(attr, arg.V)...)
 	}
 	if len(args) > 0 {
 		mid = "(" + strings.Join(args, ",") + ")"
@@ -224,25 +210,25 @@ func (r *TypeRegistry) Convert(typ schema.Type) (*Type, error) {
 			if v == 0 && len(s.Attrs) == 0 {
 				break
 			}
-			i := strconv.Itoa(v)
-			s.Attrs = append([]*Attr{LitAttr(attr.Name, i)}, s.Attrs...)
+			s.Attrs = append([]*Attr{IntAttr(attr.Name, v)}, s.Attrs...)
 		case reflect.Bool:
 			v := field.Bool()
 			if !v && len(s.Attrs) == 0 {
 				break
 			}
-			b := strconv.FormatBool(v)
-			s.Attrs = append([]*Attr{LitAttr(attr.Name, b)}, s.Attrs...)
+			s.Attrs = append([]*Attr{BoolAttr(attr.Name, v)}, s.Attrs...)
+		case reflect.String:
+			v := field.String()
+			if v == "" && len(s.Attrs) == 0 {
+				break
+			}
+			s.Attrs = append([]*Attr{StringAttr(attr.Name, v)}, s.Attrs...)
 		case reflect.Slice:
-			lits := make([]string, 0, field.Len())
-			for i := 0; i < field.Len(); i++ {
-				fi := field.Index(i)
-				if fi.Kind() != reflect.String {
-					return nil, errors.New("specutil: only string slices currently supported")
-				}
-				lits = append(lits, strconv.Quote(fi.String()))
+			vs, ok := field.Interface().([]string)
+			if !ok {
+				return nil, fmt.Errorf("specutil: unsupported slice type %T", field.Interface())
 			}
-			s.Attrs = append([]*Attr{ListAttr(attr.Name, lits...)}, s.Attrs...)
+			s.Attrs = append([]*Attr{StringsAttr(attr.Name, vs...)}, s.Attrs...)
 		default:
 			return nil, fmt.Errorf("specutil: unsupported attr kind %s for attribute %q of %q", attr.Kind, attr.Name, typeSpec.Name)
 		}
diff --git a/sql/internal/specutil/convert.go b/sql/internal/specutil/convert.go
index 538cd522..be5b9826 100644
--- a/sql/internal/specutil/convert.go
+++ b/sql/internal/specutil/convert.go
@@ -14,6 +14,8 @@ import (
 	"ariga.io/atlas/sql/internal/sqlx"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlspec"
+
+	"github.com/zclconf/go-cty/cty"
 )
 
 // List of convert function types.
@@ -133,12 +135,20 @@ func Column(spec *sqlspec.Column, conv ConvertTypeFunc) (*schema.Column, error)
 			Null: spec.Null,
 		},
 	}
-	if spec.Default != nil {
-		switch d := spec.Default.(type) {
-		case *schemahcl.LiteralValue:
-			out.Default = &schema.Literal{V: d.V}
-		case *schemahcl.RawExpr:
-			out.Default = &schema.RawExpr{X: d.X}
+	if d := spec.Default; !d.IsNull() {
+		switch {
+		case d.Type() == cty.String:
+			out.Default = &schema.Literal{V: d.AsString()}
+		case d.Type() == cty.Number:
+			out.Default = &schema.Literal{V: d.AsBigFloat().String()}
+		case d.Type() == cty.Bool:
+			out.Default = &schema.Literal{V: strconv.FormatBool(d.True())}
+		case d.Type().IsCapsuleType():
+			x, ok := d.EncapsulatedValue().(*schemahcl.RawExpr)
+			if !ok {
+				return nil, fmt.Errorf("invalid default value %q", d.Type().FriendlyName())
+			}
+			out.Default = &schema.RawExpr{X: x.X}
 		default:
 			return nil, fmt.Errorf("unsupported value type for default: %T", d)
 		}
@@ -368,7 +378,7 @@ func FromColumn(col *schema.Column, columnTypeSpec ColumnTypeSpecFunc) (*sqlspec
 		},
 	}
 	if col.Default != nil {
-		lv, err := toValue(col.Default)
+		lv, err := ExprValue(col.Default)
 		if err != nil {
 			return nil, err
 		}
@@ -383,7 +393,7 @@ func FromGenExpr(x schema.GeneratedExpr, t func(string) string) *schemahcl.Resou
 	return &schemahcl.Resource{
 		Type: "as",
 		Attrs: []*schemahcl.Attr{
-			StrAttr("expr", x.Expr),
+			schemahcl.StringAttr("expr", x.Expr),
 			VarAttr("type", t(x.Type)),
 		},
 	}
@@ -421,37 +431,44 @@ func ConvertGenExpr(r *schemahcl.Resource, c *schema.Column, t func(string) stri
 	return nil
 }
 
-func toValue(expr schema.Expr) (schemahcl.Value, error) {
-	var (
-		v   string
-		err error
-	)
-	switch expr := expr.(type) {
+// ExprValue converts a schema.Expr to a cty.Value.
+func ExprValue(expr schema.Expr) (cty.Value, error) {
+	switch x := expr.(type) {
 	case *schema.RawExpr:
-		return &schemahcl.RawExpr{X: expr.X}, nil
+		return schemahcl.RawExprValue(&schemahcl.RawExpr{X: x.X}), nil
 	case *schema.Literal:
-		v, err = normalizeQuotes(expr.V)
-		if err != nil {
-			return nil, err
+		switch {
+		case oneOfPrefix(x.V, "0x", "0X", "0b", "0B", "b'", "B'", "x'", "X'"):
+			return schemahcl.RawExprValue(&schemahcl.RawExpr{X: x.V}), nil
+		case sqlx.IsQuoted(x.V, '\'', '"'):
+			// Normalize single quotes to double quotes.
+			s, err := sqlx.Unquote(x.V)
+			if err != nil {
+				return cty.NilVal, err
+			}
+			return cty.StringVal(s), nil
+		case x.V == "true", x.V == "false":
+			return cty.BoolVal(x.V == "true"), nil
+		case strings.Contains(x.V, "."):
+			f, err := strconv.ParseFloat(x.V, 64)
+			if err != nil {
+				return cty.NilVal, err
+			}
+			return cty.NumberFloatVal(f), nil
+		case sqlx.IsLiteralNumber(x.V):
+			i, err := strconv.ParseInt(x.V, 10, 64)
+			if err != nil {
+				return cty.NilVal, err
+			}
+			return cty.NumberIntVal(i), nil
+		default:
+			return cty.NilVal, fmt.Errorf("unsupported literal value %q", x.V)
 		}
-		return &schemahcl.LiteralValue{V: v}, nil
 	default:
-		return nil, fmt.Errorf("converting expr %T to literal value", expr)
+		return cty.NilVal, fmt.Errorf("converting expr %T to literal value", expr)
 	}
 }
 
-func normalizeQuotes(s string) (string, error) {
-	if len(s) < 2 {
-		return s, nil
-	}
-	// If string is quoted with single quotes:
-	if strings.HasPrefix(s, `'`) && strings.HasSuffix(s, `'`) {
-		uq := strings.ReplaceAll(s[1:len(s)-1], "''", "'")
-		return strconv.Quote(uq), nil
-	}
-	return s, nil
-}
-
 // FromIndex converts schema.Index to sqlspec.Index.
 func FromIndex(idx *schema.Index, partFns ...func(*schema.IndexPart, *sqlspec.IndexPart)) (*sqlspec.Index, error) {
 	spec := &sqlspec.Index{Name: idx.Name, Unique: idx.Unique}
@@ -662,7 +679,7 @@ func convertCommentFromSpec(spec Attrer, attrs *[]schema.Attr) error {
 func convertCommentFromSchema(src []schema.Attr, trgt *[]*schemahcl.Attr) {
 	var c schema.Comment
 	if sqlx.Has(src, &c) {
-		*trgt = append(*trgt, StrAttr("comment", c.Text))
+		*trgt = append(*trgt, schemahcl.StringAttr("comment", c.Text))
 	}
 }
 
@@ -682,3 +699,12 @@ func Var(s string) string { return strings.ReplaceAll(s, " ", "_") }
 
 // FromVar is the inverse function of Var.
 func FromVar(s string) string { return strings.ReplaceAll(s, "_", " ") }
+
+func oneOfPrefix(s string, ps ...string) bool {
+	for _, p := range ps {
+		if strings.HasPrefix(s, p) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/sql/internal/specutil/spec.go b/sql/internal/specutil/spec.go
index d9df06a8..81034064 100644
--- a/sql/internal/specutil/spec.go
+++ b/sql/internal/specutil/spec.go
@@ -6,7 +6,6 @@ package specutil
 
 import (
 	"fmt"
-	"strconv"
 
 	"ariga.io/atlas/schemahcl"
 	"ariga.io/atlas/sql/schema"
@@ -16,77 +15,9 @@ import (
 	"github.com/zclconf/go-cty/cty"
 )
 
-// StrAttr is a helper method for constructing *schemahcl.Attr of type string.
-func StrAttr(k, v string) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.LiteralValue{V: strconv.Quote(v)},
-	}
-}
-
-// BoolAttr is a helper method for constructing *schemahcl.Attr of type bool.
-func BoolAttr(k string, v bool) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.LiteralValue{V: strconv.FormatBool(v)},
-	}
-}
-
-// IntAttr is a helper method for constructing *schemahcl.Attr with the numeric value of v.
-func IntAttr(k string, v int) *schemahcl.Attr {
-	return Int64Attr(k, int64(v))
-}
-
-// Int64Attr is a helper method for constructing *schemahcl.Attr with the numeric value of v.
-func Int64Attr(k string, v int64) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.LiteralValue{V: strconv.FormatInt(v, 10)},
-	}
-}
-
-// LitAttr is a helper method for constructing *schemahcl.Attr instances that contain literal values.
-func LitAttr(k, v string) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.LiteralValue{V: v},
-	}
-}
-
-// RawAttr is a helper method for constructing *schemahcl.Attr instances that contain sql expressions.
-func RawAttr(k, v string) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.RawExpr{X: v},
-	}
-}
-
 // VarAttr is a helper method for constructing *schemahcl.Attr instances that contain a variable reference.
 func VarAttr(k, v string) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: &schemahcl.Ref{V: v},
-	}
-}
-
-// RefAttr is a helper method for constructing *schemahcl.Attr instances that contain a reference.
-func RefAttr(k string, r *schemahcl.Ref) *schemahcl.Attr {
-	return &schemahcl.Attr{
-		K: k,
-		V: r,
-	}
-}
-
-// ListAttr is a helper method for constructing *schemahcl.Attr instances that contain list values.
-func ListAttr(k string, litValues ...string) *schemahcl.Attr {
-	lv := &schemahcl.ListValue{}
-	for _, v := range litValues {
-		lv.V = append(lv.V, &schemahcl.LiteralValue{V: v})
-	}
-	return &schemahcl.Attr{
-		K: k,
-		V: lv,
-	}
+	return schemahcl.RefAttr(k, &schemahcl.Ref{V: v})
 }
 
 type doc struct {
diff --git a/sql/mysql/convert.go b/sql/mysql/convert.go
index 1be90417..c05e4ef0 100644
--- a/sql/mysql/convert.go
+++ b/sql/mysql/convert.go
@@ -119,7 +119,7 @@ func ParseType(raw string) (schema.Type, error) {
 	case TypeBit:
 		return &BitType{
 			T:    t,
-			Size: int(size),
+			Size: size,
 		}, nil
 	// bool and booleans are synonyms for
 	// tinyint with display-width set to 1.
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index a566c0c4..7ef0a75f 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -169,11 +169,11 @@ func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error
 		return nil, err
 	}
 	if attr, ok := spec.Attr("on_update"); ok {
-		exp, ok := attr.V.(*schemahcl.RawExpr)
-		if !ok {
-			return nil, fmt.Errorf(`unexpected type %T for atrribute "on_update"`, attr.V)
+		x, err := attr.RawExpr()
+		if err != nil {
+			return nil, fmt.Errorf(`unexpected type %T for atrribute "on_update"`, attr.V.Type())
 		}
-		c.AddAttrs(&OnUpdate{A: exp.X})
+		c.AddAttrs(&OnUpdate{A: x.X})
 	}
 	if attr, ok := spec.Attr("auto_increment"); ok {
 		b, err := attr.Bool()
@@ -202,10 +202,10 @@ func schemaSpec(s *schema.Schema) (*sqlspec.Schema, []*sqlspec.Table, error) {
 		return nil, nil, err
 	}
 	if c, ok := hasCharset(s.Attrs, nil); ok {
-		sc.Extra.Attrs = append(sc.Extra.Attrs, specutil.StrAttr("charset", c))
+		sc.Extra.Attrs = append(sc.Extra.Attrs, schemahcl.StringAttr("charset", c))
 	}
 	if c, ok := hasCollate(s.Attrs, nil); ok {
-		sc.Extra.Attrs = append(sc.Extra.Attrs, specutil.StrAttr("collate", c))
+		sc.Extra.Attrs = append(sc.Extra.Attrs, schemahcl.StringAttr("collate", c))
 	}
 	return sc, t, nil
 }
@@ -224,10 +224,10 @@ func tableSpec(t *schema.Table) (*sqlspec.Table, error) {
 		return nil, err
 	}
 	if c, ok := hasCharset(t.Attrs, t.Schema.Attrs); ok {
-		ts.Extra.Attrs = append(ts.Extra.Attrs, specutil.StrAttr("charset", c))
+		ts.Extra.Attrs = append(ts.Extra.Attrs, schemahcl.StringAttr("charset", c))
 	}
 	if c, ok := hasCollate(t.Attrs, t.Schema.Attrs); ok {
-		ts.Extra.Attrs = append(ts.Extra.Attrs, specutil.StrAttr("collate", c))
+		ts.Extra.Attrs = append(ts.Extra.Attrs, schemahcl.StringAttr("collate", c))
 	}
 	return ts, nil
 }
@@ -246,7 +246,7 @@ func indexSpec(idx *schema.Index) (*sqlspec.Index, error) {
 
 func partAttr(part *schema.IndexPart, spec *sqlspec.IndexPart) {
 	if p := (SubPart{}); sqlx.Has(part.Attrs, &p) && p.Len > 0 {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.IntAttr("prefix", p.Len))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.IntAttr("prefix", p.Len))
 	}
 }
 
@@ -257,16 +257,16 @@ func columnSpec(c *schema.Column, t *schema.Table) (*sqlspec.Column, error) {
 		return nil, err
 	}
 	if c, ok := hasCharset(c.Attrs, t.Attrs); ok {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.StrAttr("charset", c))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.StringAttr("charset", c))
 	}
 	if c, ok := hasCollate(c.Attrs, t.Attrs); ok {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.StrAttr("collate", c))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.StringAttr("collate", c))
 	}
 	if o := (OnUpdate{}); sqlx.Has(c.Attrs, &o) {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.RawAttr("on_update", o.A))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.RawAttr("on_update", o.A))
 	}
 	if sqlx.Has(c.Attrs, &AutoIncrement{}) {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.BoolAttr("auto_increment", true))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.BoolAttr("auto_increment", true))
 	}
 	if x := (schema.GeneratedExpr{}); sqlx.Has(c.Attrs, &x) {
 		spec.Extra.Children = append(spec.Extra.Children, specutil.FromGenExpr(x, storedOrVirtual))
@@ -292,7 +292,7 @@ func storedOrVirtual(s string) string {
 func checkSpec(s *schema.Check) *sqlspec.Check {
 	c := specutil.FromCheck(s)
 	if e := (Enforced{}); sqlx.Has(s.Attrs, &e) {
-		c.Extra.Attrs = append(c.Extra.Attrs, specutil.BoolAttr("enforced", true))
+		c.Extra.Attrs = append(c.Extra.Attrs, schemahcl.BoolAttr("enforced", true))
 	}
 	return c
 }
@@ -372,6 +372,16 @@ var TypeRegistry = schemahcl.NewRegistry(
 				{Name: "values", Kind: reflect.Slice, Required: true},
 			},
 			RType: reflect.TypeOf(schema.EnumType{}),
+			FromSpec: func(t *schemahcl.Type) (schema.Type, error) {
+				if len(t.Attrs) != 1 || t.Attrs[0].K != "values" {
+					return nil, fmt.Errorf("invalid enum type spec: %v", t)
+				}
+				v, err := t.Attrs[0].Strings()
+				if err != nil {
+					return nil, err
+				}
+				return &schema.EnumType{T: "enum", Values: v}, nil
+			},
 		},
 		&schemahcl.TypeSpec{
 			Name: TypeSet,
@@ -380,6 +390,16 @@ var TypeRegistry = schemahcl.NewRegistry(
 				{Name: "values", Kind: reflect.Slice, Required: true},
 			},
 			RType: reflect.TypeOf(SetType{}),
+			FromSpec: func(t *schemahcl.Type) (schema.Type, error) {
+				if len(t.Attrs) != 1 || t.Attrs[0].K != "values" {
+					return nil, fmt.Errorf("invalid set type spec: %v", t)
+				}
+				v, err := t.Attrs[0].Strings()
+				if err != nil {
+					return nil, err
+				}
+				return &SetType{Values: v}, nil
+			},
 		},
 		schemahcl.NewTypeSpec(TypeBool),
 		schemahcl.NewTypeSpec(TypeBoolean),
diff --git a/sql/postgres/diff.go b/sql/postgres/diff.go
index 422e513c..e5fbdf3c 100644
--- a/sql/postgres/diff.go
+++ b/sql/postgres/diff.go
@@ -78,7 +78,7 @@ func (d *diff) defaultChanged(from, to *schema.Column) (bool, error) {
 	if ok1 != ok2 {
 		return true, nil
 	}
-	if trimCast(d1) == trimCast(d2) {
+	if trimCast(d1) == trimCast(d2) || quote(d1) == quote(d2) {
 		return false, nil
 	}
 	// Use database comparison in case of mismatch (e.g. `SELECT ARRAY[1] = '{1}'::int[]`).
diff --git a/sql/postgres/sqlspec.go b/sql/postgres/sqlspec.go
index 0b6d68f1..0b09b92c 100644
--- a/sql/postgres/sqlspec.go
+++ b/sql/postgres/sqlspec.go
@@ -208,27 +208,27 @@ func fromPartition(p Partition) *schemahcl.Resource {
 			specutil.VarAttr("type", strings.ToUpper(specutil.Var(p.T))),
 		},
 	}
-	columns, ok := func() (*schemahcl.ListValue, bool) {
-		parts := make([]schemahcl.Value, 0, len(p.Parts))
+	columns, ok := func() ([]*schemahcl.Ref, bool) {
+		parts := make([]*schemahcl.Ref, 0, len(p.Parts))
 		for _, p := range p.Parts {
 			if p.C == nil {
 				return nil, false
 			}
 			parts = append(parts, specutil.ColumnRef(p.C.Name))
 		}
-		return &schemahcl.ListValue{V: parts}, true
+		return parts, true
 	}()
 	if ok {
-		key.Attrs = append(key.Attrs, &schemahcl.Attr{K: "columns", V: columns})
+		key.Attrs = append(key.Attrs, schemahcl.RefsAttr("columns", columns...))
 		return key
 	}
 	for _, p := range p.Parts {
 		part := &schemahcl.Resource{Type: "by"}
 		switch {
 		case p.C != nil:
-			part.Attrs = append(part.Attrs, specutil.RefAttr("column", specutil.ColumnRef(p.C.Name)))
+			part.Attrs = append(part.Attrs, schemahcl.RefAttr("column", specutil.ColumnRef(p.C.Name)))
 		case p.X != nil:
-			part.Attrs = append(part.Attrs, specutil.StrAttr("expr", p.X.(*schema.RawExpr).X))
+			part.Attrs = append(part.Attrs, schemahcl.StringAttr("expr", p.X.(*schema.RawExpr).X))
 		}
 		key.Children = append(key.Children, part)
 	}
@@ -237,7 +237,7 @@ func fromPartition(p Partition) *schemahcl.Resource {
 
 // convertColumn converts a sqlspec.Column into a schema.Column.
 func convertColumn(spec *sqlspec.Column, _ *schema.Table) (*schema.Column, error) {
-	if err := fixDefaultQuotes(spec.Default); err != nil {
+	if err := fixDefaultQuotes(spec); err != nil {
 		return nil, err
 	}
 	c, err := specutil.Column(spec, convertColumnType)
@@ -278,17 +278,17 @@ func convertIdentity(r *schemahcl.Resource) (*Identity, error) {
 
 // fixDefaultQuotes fixes the quotes on the Default field to be single quotes
 // instead of double quotes.
-func fixDefaultQuotes(value schemahcl.Value) error {
-	lv, ok := value.(*schemahcl.LiteralValue)
-	if !ok {
+func fixDefaultQuotes(spec *sqlspec.Column) error {
+	if spec.Default.Type() != cty.String {
 		return nil
 	}
-	if sqlx.IsQuoted(lv.V, '"') {
-		uq, err := strconv.Unquote(lv.V)
+	if s := spec.Default.AsString(); sqlx.IsQuoted(s, '"') {
+		uq, err := strconv.Unquote(s)
 		if err != nil {
 			return err
 		}
-		lv.V = "'" + uq + "'"
+		s = "'" + uq + "'"
+		spec.Default = cty.StringVal(s)
 	}
 	return nil
 }
@@ -501,20 +501,17 @@ func indexSpec(idx *schema.Index) (*sqlspec.Index, error) {
 		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.VarAttr("type", strings.ToUpper(i.T)))
 	}
 	if i := (IndexInclude{}); sqlx.Has(idx.Attrs, &i) && len(i.Columns) > 0 {
-		attr := &schemahcl.ListValue{}
+		refs := make([]*schemahcl.Ref, 0, len(i.Columns))
 		for _, c := range i.Columns {
-			attr.V = append(attr.V, specutil.ColumnRef(c.Name))
+			refs = append(refs, specutil.ColumnRef(c.Name))
 		}
-		spec.Extra.Attrs = append(spec.Extra.Attrs, &schemahcl.Attr{
-			K: "include",
-			V: attr,
-		})
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.RefsAttr("include", refs...))
 	}
 	if i := (IndexPredicate{}); sqlx.Has(idx.Attrs, &i) && i.P != "" {
 		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.VarAttr("where", strconv.Quote(i.P)))
 	}
 	if p, ok := indexStorageParams(idx.Attrs); ok {
-		spec.Extra.Attrs = append(spec.Extra.Attrs, specutil.Int64Attr("page_per_range", p.PagesPerRange))
+		spec.Extra.Attrs = append(spec.Extra.Attrs, schemahcl.Int64Attr("page_per_range", p.PagesPerRange))
 	}
 	return spec, nil
 }
@@ -544,10 +541,10 @@ func fromIdentity(i *Identity) *schemahcl.Resource {
 	}
 	if s := i.Sequence; s != nil {
 		if s.Start != 1 {
-			id.Attrs = append(id.Attrs, specutil.Int64Attr("start", s.Start))
+			id.Attrs = append(id.Attrs, schemahcl.Int64Attr("start", s.Start))
 		}
 		if s.Increment != 1 {
-			id.Attrs = append(id.Attrs, specutil.Int64Attr("increment", s.Increment))
+			id.Attrs = append(id.Attrs, schemahcl.Int64Attr("increment", s.Increment))
 		}
 	}
 	return id
@@ -640,7 +637,7 @@ var TypeRegistry = schemahcl.NewRegistry(
 					spec.T = specutil.Var(strings.ToLower(i.F))
 				}
 				if p := i.Precision; p != nil && *p != defaultTimePrecision {
-					spec.Attrs = []*schemahcl.Attr{specutil.IntAttr("precision", *p)}
+					spec.Attrs = []*schemahcl.Attr{schemahcl.IntAttr("precision", *p)}
 				}
 				return spec, nil
 			}),
@@ -692,7 +689,7 @@ func typeSpec(t schema.Type) (*schemahcl.Type, error) {
 	if t, ok := t.(*schema.TimeType); ok && t.T != TypeDate {
 		spec := &schemahcl.Type{T: timeAlias(t.T)}
 		if p := t.Precision; p != nil && *p != defaultTimePrecision {
-			spec.Attrs = []*schemahcl.Attr{specutil.IntAttr("precision", *p)}
+			spec.Attrs = []*schemahcl.Attr{schemahcl.IntAttr("precision", *p)}
 		}
 		return spec, nil
 	}
diff --git a/sql/sqlite/sqlspec.go b/sql/sqlite/sqlspec.go
index c95b6fbf..fa3b8fa5 100644
--- a/sql/sqlite/sqlspec.go
+++ b/sql/sqlite/sqlspec.go
@@ -137,7 +137,7 @@ func columnSpec(c *schema.Column, _ *schema.Table) (*sqlspec.Column, error) {
 		return nil, err
 	}
 	if sqlx.Has(c.Attrs, &AutoIncrement{}) {
-		s.Extra.Attrs = append(s.Extra.Attrs, specutil.BoolAttr("auto_increment", true))
+		s.Extra.Attrs = append(s.Extra.Attrs, schemahcl.BoolAttr("auto_increment", true))
 	}
 	if x := (schema.GeneratedExpr{}); sqlx.Has(c.Attrs, &x) {
 		s.Extra.Children = append(s.Extra.Children, specutil.FromGenExpr(x, storedOrVirtual))
diff --git a/sql/sqlspec/sqlspec.go b/sql/sqlspec/sqlspec.go
index 21625713..580d4e06 100644
--- a/sql/sqlspec/sqlspec.go
+++ b/sql/sqlspec/sqlspec.go
@@ -6,6 +6,8 @@ package sqlspec
 
 import (
 	"ariga.io/atlas/schemahcl"
+
+	"github.com/zclconf/go-cty/cty"
 )
 
 type (
@@ -33,7 +35,7 @@ type (
 		Name    string          `spec:",name"`
 		Null    bool            `spec:"null"`
 		Type    *schemahcl.Type `spec:"type"`
-		Default schemahcl.Value `spec:"default"`
+		Default cty.Value       `spec:"default"`
 		schemahcl.DefaultExtension
 	}
 


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/project_test.go b/cmd/atlas/internal/cmdapi/project_test.go
index 99daef49..fd728f31 100644
--- a/cmd/atlas/internal/cmdapi/project_test.go
+++ b/cmd/atlas/internal/cmdapi/project_test.go
@@ -99,7 +99,7 @@ env "multi" {
 							{
 								Type: "destructive",
 								Attrs: []*schemahcl.Attr{
-									{K: "error", V: &schemahcl.LiteralValue{V: "true"}},
+									schemahcl.BoolAttr("error", true),
 								},
 							},
 						},
@@ -109,10 +109,10 @@ env "multi" {
 			DefaultExtension: schemahcl.DefaultExtension{
 				Extra: schemahcl.Resource{
 					Attrs: []*schemahcl.Attr{
-						{K: "bool", V: &schemahcl.LiteralValue{V: "true"}},
-						{K: "integer", V: &schemahcl.LiteralValue{V: "42"}},
-						{K: "src", V: &schemahcl.LiteralValue{V: `"./app.hcl"`}},
-						{K: "str", V: &schemahcl.LiteralValue{V: `"hello"`}},
+						schemahcl.BoolAttr("bool", true),
+						schemahcl.IntAttr("integer", 42),
+						schemahcl.StringAttr("src", "./app.hcl"),
+						schemahcl.StringAttr("str", "hello"),
 					},
 				},
 			},
diff --git a/internal/integration/hclsqlspec/hclsqlspec_test.go b/internal/integration/hclsqlspec/hclsqlspec_test.go
index e00af199..8a76605c 100644
--- a/internal/integration/hclsqlspec/hclsqlspec_test.go
+++ b/internal/integration/hclsqlspec/hclsqlspec_test.go
@@ -5,6 +5,7 @@
 package hclsqlspec
 
 import (
+	"math/big"
 	"testing"
 
 	"github.com/zclconf/go-cty/cty"
@@ -148,25 +149,25 @@ table "accounts" {
 						Name:    "id",
 						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "123"},
+						Default: cty.NumberVal(big.NewFloat(123).SetPrec(512)),
 					},
 					{
 						Name:    "age",
 						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "10"},
+						Default: cty.NumberVal(big.NewFloat(10).SetPrec(512)),
 					},
 					{
 						Name:    "active",
 						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "true"},
+						Default: cty.BoolVal(true),
 					},
 					{
 						Name:    "account_active",
 						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "true"},
+						Default: cty.BoolVal(true),
 					},
 				},
 				PrimaryKey: &sqlspec.PrimaryKey{
@@ -224,25 +225,25 @@ table "accounts" {
 						Name:    "id",
 						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "123"},
+						Default: cty.NumberVal(big.NewFloat(123).SetPrec(512)),
 					},
 					{
 						Name:    "age",
 						Type:    &schemahcl.Type{T: "int"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "10"},
+						Default: cty.NumberVal(big.NewFloat(10).SetPrec(512)),
 					},
 					{
 						Name:    "active",
 						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "true"},
+						Default: cty.BoolVal(true),
 					},
 					{
 						Name:    "user_active",
 						Type:    &schemahcl.Type{T: "boolean"},
 						Null:    false,
-						Default: &schemahcl.LiteralValue{V: "true"},
+						Default: cty.BoolVal(true),
 					},
 				},
 				PrimaryKey: &sqlspec.PrimaryKey{
@@ -307,7 +308,7 @@ schema "hi" {
 				DefaultExtension: schemahcl.DefaultExtension{
 					Extra: schemahcl.Resource{
 						Attrs: []*schemahcl.Attr{
-							{K: "x", V: &schemahcl.LiteralValue{V: "1"}},
+							schemahcl.IntAttr("x", 1),
 						},
 					},
 				},
diff --git a/schemahcl/context_test.go b/schemahcl/context_test.go
index d5cac59b..decf66f7 100644
--- a/schemahcl/context_test.go
+++ b/schemahcl/context_test.go
@@ -381,33 +381,25 @@ variadic = enum("a","b","c")
 	require.NoError(t, err)
 	require.EqualValues(t, "int", test.First.T)
 	require.EqualValues(t, "bool", test.Second.T)
-	require.EqualValues(t, &Type{
-		T: "varchar",
-		Attrs: []*Attr{
-			{K: "size", V: &LiteralValue{V: "255"}},
-		},
-	}, test.Varchar)
-	require.EqualValues(t, &Type{
-		T: "enum",
-		Attrs: []*Attr{
-			{
-				K: "values",
-				V: &ListValue{
-					V: []Value{
-						&LiteralValue{V: `"a"`},
-						&LiteralValue{V: `"b"`},
-						&LiteralValue{V: `"c"`},
-					},
-				},
-			},
-		},
-	}, test.Variadic)
-	require.EqualValues(t, &Type{
-		T: "int",
-		Attrs: []*Attr{
-			{K: "size", V: &LiteralValue{V: "10"}},
-		},
-	}, test.Third)
+
+	require.EqualValues(t, "varchar", test.Varchar.T)
+	require.Len(t, test.Varchar.Attrs, 1)
+	i, err := test.Varchar.Attrs[0].Int()
+	require.NoError(t, err)
+	require.EqualValues(t, 255, i)
+
+	require.EqualValues(t, "enum", test.Variadic.T)
+	require.Len(t, test.Variadic.Attrs, 1)
+	vs, err := test.Variadic.Attrs[0].Strings()
+	require.NoError(t, err)
+	require.EqualValues(t, []string{"a", "b", "c"}, vs)
+
+	require.EqualValues(t, "int", test.Third.T)
+	require.Len(t, test.Third.Attrs, 1)
+	i, err = test.Third.Attrs[0].Int()
+	require.NoError(t, err)
+	require.EqualValues(t, 10, i)
+
 	after, err := s.MarshalSpec(&test)
 	require.NoError(t, err)
 	require.EqualValues(t, f, string(after))
@@ -445,13 +437,22 @@ arg_2 = float(10,2)
 	err := s.EvalBytes([]byte(f), &test, nil)
 	require.NoError(t, err)
 	require.Nil(t, test.Arg0.Attrs)
-	require.EqualValues(t, []*Attr{
-		LitAttr("precision", "10"),
-	}, test.Arg1.Attrs)
-	require.EqualValues(t, []*Attr{
-		LitAttr("precision", "10"),
-		LitAttr("scale", "2"),
-	}, test.Arg2.Attrs)
+
+	require.Len(t, test.Arg1.Attrs, 1)
+	require.Equal(t, "precision", test.Arg1.Attrs[0].K)
+	i, err := test.Arg1.Attrs[0].Int()
+	require.NoError(t, err)
+	require.EqualValues(t, 10, i)
+
+	require.Len(t, test.Arg2.Attrs, 2)
+	require.Equal(t, "precision", test.Arg2.Attrs[0].K)
+	i, err = test.Arg2.Attrs[0].Int()
+	require.NoError(t, err)
+	require.EqualValues(t, 10, i)
+	require.Equal(t, "scale", test.Arg2.Attrs[1].K)
+	i, err = test.Arg2.Attrs[1].Int()
+	require.NoError(t, err)
+	require.EqualValues(t, 2, i)
 }
 
 func TestQualifiedRefs(t *testing.T) {
diff --git a/schemahcl/extension_test.go b/schemahcl/extension_test.go
index b6fa5fdb..6d59f662 100644
--- a/schemahcl/extension_test.go
+++ b/schemahcl/extension_test.go
@@ -8,19 +8,21 @@ import (
 	"testing"
 
 	"ariga.io/atlas/schemahcl"
+
 	"github.com/stretchr/testify/require"
+	"github.com/zclconf/go-cty/cty"
 )
 
 type OwnerBlock struct {
 	schemahcl.DefaultExtension
-	ID        string                  `spec:",name"`
-	FirstName string                  `spec:"first_name"`
-	Born      int                     `spec:"born"`
-	Active    bool                    `spec:"active"`
-	BoolPtr   *bool                   `spec:"bool_ptr"`
-	OmitBool1 bool                    `spec:"omit_bool1,omitempty"`
-	OmitBool2 bool                    `spec:"omit_bool2,omitempty"`
-	Lit       *schemahcl.LiteralValue `spec:"lit"`
+	ID        string    `spec:",name"`
+	FirstName string    `spec:"first_name"`
+	Born      int       `spec:"born"`
+	Active    bool      `spec:"active"`
+	BoolPtr   *bool     `spec:"bool_ptr"`
+	OmitBool1 bool      `spec:"omit_bool1,omitempty"`
+	OmitBool2 bool      `spec:"omit_bool2,omitempty"`
+	Lit       cty.Value `spec:"lit"`
 }
 
 type PetBlock struct {
@@ -47,13 +49,13 @@ func TestExtension(t *testing.T) {
 		Name: "name",
 		Type: "owner",
 		Attrs: []*schemahcl.Attr{
-			schemahcl.StrLitAttr("first_name", "tzuri"),
-			schemahcl.LitAttr("born", "2019"),
-			schemahcl.LitAttr("active", "true"),
-			schemahcl.LitAttr("bool_ptr", "true"),
-			schemahcl.LitAttr("omit_bool1", "true"),
-			schemahcl.LitAttr("lit", "1000"),
-			schemahcl.LitAttr("extra", "true"),
+			schemahcl.StringAttr("first_name", "tzuri"),
+			schemahcl.IntAttr("born", 2019),
+			schemahcl.BoolAttr("active", true),
+			schemahcl.BoolAttr("bool_ptr", true),
+			schemahcl.BoolAttr("omit_bool1", true),
+			schemahcl.StringAttr("lit", "1000"),
+			schemahcl.BoolAttr("extra", true),
 		},
 		Children: []*schemahcl.Resource{
 			{
@@ -71,7 +73,7 @@ func TestExtension(t *testing.T) {
 	require.EqualValues(t, true, owner.Active)
 	require.NotNil(t, owner.BoolPtr)
 	require.EqualValues(t, true, *owner.BoolPtr)
-	require.EqualValues(t, schemahcl.LitAttr("lit", "1000").V, owner.Lit)
+	require.EqualValues(t, cty.StringVal("1000"), owner.Lit)
 	attr, ok := owner.Remain().Attr("extra")
 	require.True(t, ok)
 	eb, err := attr.Bool()
@@ -90,25 +92,25 @@ func TestNested(t *testing.T) {
 		Name: "donut",
 		Type: "pet",
 		Attrs: []*schemahcl.Attr{
-			schemahcl.StrLitAttr("breed", "golden retriever"),
-			schemahcl.LitAttr("born", "2002"),
+			schemahcl.StringAttr("breed", "golden retriever"),
+			schemahcl.IntAttr("born", 2002),
 		},
 		Children: []*schemahcl.Resource{
 			{
 				Name: "rotemtam",
 				Type: "owner",
 				Attrs: []*schemahcl.Attr{
-					schemahcl.StrLitAttr("first_name", "rotem"),
-					schemahcl.LitAttr("born", "1985"),
-					schemahcl.LitAttr("active", "true"),
+					schemahcl.StringAttr("first_name", "rotem"),
+					schemahcl.IntAttr("born", 1985),
+					schemahcl.BoolAttr("active", true),
 				},
 			},
 			{
 				Name: "gonnie",
 				Type: "role_model",
 				Attrs: []*schemahcl.Attr{
-					schemahcl.StrLitAttr("breed", "golden retriever"),
-					schemahcl.LitAttr("born", "1998"),
+					schemahcl.StringAttr("breed", "golden retriever"),
+					schemahcl.IntAttr("born", 1998),
 				},
 			},
 		},
@@ -148,10 +150,7 @@ func TestRef(t *testing.T) {
 		Name: "x",
 		Type: "a",
 		Attrs: []*schemahcl.Attr{
-			{
-				K: "user",
-				V: &schemahcl.Ref{V: "$user.rotemtam"},
-			},
+			schemahcl.RefAttr("user", &schemahcl.Ref{V: "$user.rotemtam"}),
 		},
 	}
 	var a A
@@ -174,15 +173,11 @@ func TestListRef(t *testing.T) {
 		Name: "x",
 		Type: "b",
 		Attrs: []*schemahcl.Attr{
-			{
-				K: "users",
-				V: &schemahcl.ListValue{
-					V: []schemahcl.Value{
-						&schemahcl.Ref{V: "$user.a8m"},
-						&schemahcl.Ref{V: "$user.rotemtam"},
-					},
-				},
-			},
+			schemahcl.RefsAttr(
+				"users",
+				&schemahcl.Ref{V: "$user.a8m"},
+				&schemahcl.Ref{V: "$user.rotemtam"},
+			),
 		},
 	}
 
@@ -209,7 +204,7 @@ func TestNameAttr(t *testing.T) {
 		Name: "id",
 		Type: "named",
 		Attrs: []*schemahcl.Attr{
-			schemahcl.StrLitAttr("name", "atlas"),
+			schemahcl.StringAttr("name", "atlas"),
 		},
 	}
 	var tgt Named
diff --git a/schemahcl/hcl_test.go b/schemahcl/hcl_test.go
index 56d9fa95..d98ab405 100644
--- a/schemahcl/hcl_test.go
+++ b/schemahcl/hcl_test.go
@@ -361,83 +361,6 @@ func TestMultiFile(t *testing.T) {
 	}, test.People[1])
 }
 
-func TestDynamicBlocks(t *testing.T) {
-	type (
-		URL struct {
-			Value string `spec:"value"`
-		}
-		Env struct {
-			Name string `spec:",name"`
-			URLs []*URL `spec:"url"`
-		}
-	)
-	var (
-		doc struct {
-			Envs []*Env `spec:"env"`
-		}
-		b = []byte(`
-variable "tenants" {
-  type    = list(string)
-  default = ["atlas", "ent"]
-}
-
-variable "domains" {
-  type = list(string)
-}
-
-env "prod" {
-  dynamic "url" {
-    for_each = var.tenants
-    content {
-      value = "mysql://root:pass@:3306/${url.value}"
-    }
-  }
-  migration {
-    dir = "file://migrations"
-  }
-}
-
-env "staging" {
-  dynamic "url" {
-    for_each = [for t in var.tenants: "mysql://root:pass@:3306/${t}"]
-    content {
-      value = "${url.value}"
-    }
-  }
-  migration {
-    dir = "file://migrations"
-  }
-}
-`)
-	)
-	require.NoError(t, New().EvalBytes(b, &doc, map[string]cty.Value{
-		"domains": cty.ListVal([]cty.Value{
-			cty.StringVal("a"),
-			cty.StringVal("b"),
-		}),
-	}))
-	require.Len(t, doc.Envs, 2)
-	require.Equal(t, "prod", doc.Envs[0].Name)
-	require.Equal(t, "staging", doc.Envs[1].Name)
-	require.Len(t, doc.Envs[0].URLs, 2)
-	require.Len(t, doc.Envs[1].URLs, 2)
-	require.Equal(t, "mysql://root:pass@:3306/atlas", doc.Envs[0].URLs[0].Value)
-	require.Equal(t, "mysql://root:pass@:3306/atlas", doc.Envs[1].URLs[0].Value)
-	require.Equal(t, "mysql://root:pass@:3306/ent", doc.Envs[0].URLs[1].Value)
-	require.Equal(t, "mysql://root:pass@:3306/ent", doc.Envs[1].URLs[1].Value)
-
-	// A one-element is allowed for list types.
-	require.NoError(t, New().EvalBytes(b, &doc, map[string]cty.Value{
-		"domains": cty.StringVal("a"),
-	}))
-
-	// Mismatched element types.
-	err := New().EvalBytes(b, &doc, map[string]cty.Value{
-		"domains": cty.BoolVal(false),
-	})
-	require.EqualError(t, err, `variable "domains": list of string required`)
-}
-
 func TestForEachResources(t *testing.T) {
 	type (
 		Env struct {
diff --git a/schemahcl/spec_test.go b/schemahcl/spec_test.go
deleted file mode 100644
index d92cc73e..00000000
--- a/schemahcl/spec_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package schemahcl
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestBoolVal(t *testing.T) {
-	b, err := BoolVal(&LiteralValue{V: "true"})
-	require.NoError(t, err)
-	require.True(t, b)
-}
-
-func TestBools(t *testing.T) {
-	a := Attr{
-		K: "b",
-		V: &ListValue{
-			V: []Value{
-				&LiteralValue{V: "true"},
-				&LiteralValue{V: "false"},
-				&LiteralValue{V: "true"},
-			},
-		},
-	}
-	bls, err := a.Bools()
-	require.NoError(t, err)
-	require.EqualValues(t, []bool{true, false, true}, bls)
-}
diff --git a/schemahcl/types_test.go b/schemahcl/types_test.go
index 1e6d473f..d714c28f 100644
--- a/schemahcl/types_test.go
+++ b/schemahcl/types_test.go
@@ -33,7 +33,7 @@ func TestTypePrint(t *testing.T) {
 		},
 		{
 			spec:     intSpec,
-			typ:      &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
+			typ:      &Type{T: "int", Attrs: []*Attr{BoolAttr("unsigned", true)}},
 			expected: "int unsigned",
 		},
 		{
@@ -42,7 +42,7 @@ func TestTypePrint(t *testing.T) {
 				T:          "float",
 				Attributes: []*TypeAttr{unsignedTypeAttr()},
 			},
-			typ:      &Type{T: "float", Attrs: []*Attr{LitAttr("unsigned", "true")}},
+			typ:      &Type{T: "float", Attrs: []*Attr{BoolAttr("unsigned", true)}},
 			expected: "float unsigned",
 		},
 		{
@@ -53,7 +53,7 @@ func TestTypePrint(t *testing.T) {
 					{Name: "size", Kind: reflect.Int, Required: true},
 				},
 			},
-			typ:      &Type{T: "varchar", Attrs: []*Attr{LitAttr("size", "255")}},
+			typ:      &Type{T: "varchar", Attrs: []*Attr{IntAttr("size", 255)}},
 			expected: "varchar(255)",
 		},
 	} {
@@ -171,34 +171,34 @@ func TestRegistryConvert(t *testing.T) {
 	}{
 		{
 			typ:      &schema.StringType{T: "varchar", Size: 255},
-			expected: &Type{T: "varchar", Attrs: []*Attr{LitAttr("size", "255")}},
+			expected: &Type{T: "varchar", Attrs: []*Attr{IntAttr("size", 255)}},
 		},
 		{
 			typ:      &schema.IntegerType{T: "int", Unsigned: true},
-			expected: &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
+			expected: &Type{T: "int", Attrs: []*Attr{BoolAttr("unsigned", true)}},
 		},
 		{
 			typ:      &schema.IntegerType{T: "int", Unsigned: true},
-			expected: &Type{T: "int", Attrs: []*Attr{LitAttr("unsigned", "true")}},
+			expected: &Type{T: "int", Attrs: []*Attr{BoolAttr("unsigned", true)}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Precision: 10, Scale: 2}, // decimal(10,2)
 			expected: &Type{T: "decimal", Attrs: []*Attr{
-				LitAttr("precision", "10"),
-				LitAttr("scale", "2"),
+				IntAttr("precision", 10),
+				IntAttr("scale", 2),
 			}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Precision: 10}, // decimal(10)
 			expected: &Type{T: "decimal", Attrs: []*Attr{
-				LitAttr("precision", "10"),
+				IntAttr("precision", 10),
 			}},
 		},
 		{
 			typ: &schema.DecimalType{T: "decimal", Scale: 2}, // decimal(0,2)
 			expected: &Type{T: "decimal", Attrs: []*Attr{
-				LitAttr("precision", "0"),
-				LitAttr("scale", "2"),
+				IntAttr("precision", 0),
+				IntAttr("scale", 2),
 			}},
 		},
 		{
@@ -208,7 +208,7 @@ func TestRegistryConvert(t *testing.T) {
 		{
 			typ: &schema.EnumType{T: "enum", Values: []string{"on", "off"}},
 			expected: &Type{T: "enum", Attrs: []*Attr{
-				ListAttr("values", `"on"`, `"off"`),
+				StringsAttr("values", "on", "off"),
 			}},
 		},
 		{
diff --git a/sql/internal/spectest/spectest.go b/sql/internal/spectest/spectest.go
index 034223c9..0cb27ca8 100644
--- a/sql/internal/spectest/spectest.go
+++ b/sql/internal/spectest/spectest.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 
 	"ariga.io/atlas/schemahcl"
-	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/schema"
 
 	"github.com/hashicorp/hcl/v2/hclparse"
@@ -87,13 +86,13 @@ func dummyType(t *testing.T, ts *schemahcl.TypeSpec) *schemahcl.Type {
 		var a *schemahcl.Attr
 		switch attr.Kind {
 		case reflect.Int, reflect.Int64:
-			a = specutil.LitAttr(attr.Name, "2")
+			a = schemahcl.IntAttr(attr.Name, 2)
 		case reflect.String:
-			a = specutil.LitAttr(attr.Name, `"a"`)
+			a = schemahcl.StringAttr(attr.Name, "a")
 		case reflect.Slice:
-			a = specutil.ListAttr(attr.Name, `"a"`, `"b"`)
+			a = schemahcl.StringsAttr(attr.Name, "a", "b")
 		case reflect.Bool:
-			a = specutil.LitAttr(attr.Name, "false")
+			a = schemahcl.BoolAttr(attr.Name, false)
 		default:
 			t.Fatalf("unsupported kind: %s", attr.Kind)
 		}
diff --git a/sql/sqlcheck/datadepend/datadepend_test.go b/sql/sqlcheck/datadepend/datadepend_test.go
index d2f83907..fefad1fb 100644
--- a/sql/sqlcheck/datadepend/datadepend_test.go
+++ b/sql/sqlcheck/datadepend/datadepend_test.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 
 	"ariga.io/atlas/schemahcl"
-	"ariga.io/atlas/sql/internal/specutil"
 
 	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/schema"
@@ -226,7 +225,7 @@ func TestAnalyzer_Options(t *testing.T) {
 			{
 				Type: "data_depend",
 				Attrs: []*schemahcl.Attr{
-					specutil.BoolAttr("error", true),
+					schemahcl.BoolAttr("error", true),
 				},
 			},
 		},
diff --git a/sql/sqlcheck/destructive/destructive_test.go b/sql/sqlcheck/destructive/destructive_test.go
index e8cb5ad5..063a27b3 100644
--- a/sql/sqlcheck/destructive/destructive_test.go
+++ b/sql/sqlcheck/destructive/destructive_test.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 
 	"ariga.io/atlas/schemahcl"
-	"ariga.io/atlas/sql/internal/specutil"
 	"ariga.io/atlas/sql/migrate"
 	"ariga.io/atlas/sql/schema"
 	"ariga.io/atlas/sql/sqlcheck"
@@ -168,7 +167,7 @@ func TestAnalyzer_DropSchema(t *testing.T) {
 			{
 				Type: "destructive",
 				Attrs: []*schemahcl.Attr{
-					specutil.BoolAttr("error", false),
+					schemahcl.BoolAttr("error", false),
 				},
 			},
 		},
@@ -235,7 +234,7 @@ func TestAnalyzer_DropColumn(t *testing.T) {
 			{
 				Type: "destructive",
 				Attrs: []*schemahcl.Attr{
-					specutil.BoolAttr("error", false),
+					schemahcl.BoolAttr("error", false),
 				},
 			},
 		},
diff --git a/sql/sqlite/sqlspec_test.go b/sql/sqlite/sqlspec_test.go
index 665eb4aa..9e021eb4 100644
--- a/sql/sqlite/sqlspec_test.go
+++ b/sql/sqlite/sqlspec_test.go
@@ -172,7 +172,6 @@ table "accounts" {
 			{SeqNo: 0, C: exp.Tables[1].Columns[0]},
 		},
 	}
-
 	var s schema.Schema
 	err := EvalHCLBytes([]byte(f), &s, nil)
 	require.NoError(t, err)