| package typeexpr |
| |
| import ( |
| "fmt" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| const invalidTypeSummary = "Invalid type specification" |
| |
| // getType is the internal implementation of both Type and TypeConstraint, |
| // using the passed flag to distinguish. When constraint is false, the "any" |
| // keyword will produce an error. |
| func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) { |
| // First we'll try for one of our keywords |
| kw := hcl.ExprAsKeyword(expr) |
| switch kw { |
| case "bool": |
| return cty.Bool, nil |
| case "string": |
| return cty.String, nil |
| case "number": |
| return cty.Number, nil |
| case "any": |
| if constraint { |
| return cty.DynamicPseudoType, nil |
| } |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("The keyword %q cannot be used in this type specification: an exact type is required.", kw), |
| Subject: expr.Range().Ptr(), |
| }} |
| case "list", "map", "set": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", kw), |
| Subject: expr.Range().Ptr(), |
| }} |
| case "object": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.", |
| Subject: expr.Range().Ptr(), |
| }} |
| case "tuple": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "The tuple type constructor requires one argument specifying the element types as a list.", |
| Subject: expr.Range().Ptr(), |
| }} |
| case "": |
| // okay! we'll fall through and try processing as a call, then. |
| default: |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("The keyword %q is not a valid type specification.", kw), |
| Subject: expr.Range().Ptr(), |
| }} |
| } |
| |
| // If we get down here then our expression isn't just a keyword, so we'll |
| // try to process it as a call instead. |
| call, diags := hcl.ExprCall(expr) |
| if diags.HasErrors() { |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).", |
| Subject: expr.Range().Ptr(), |
| }} |
| } |
| |
| switch call.Name { |
| case "bool", "string", "number", "any": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("Primitive type keyword %q does not expect arguments.", call.Name), |
| Subject: &call.ArgsRange, |
| }} |
| } |
| |
| if len(call.Arguments) != 1 { |
| contextRange := call.ArgsRange |
| subjectRange := call.ArgsRange |
| if len(call.Arguments) > 1 { |
| // If we have too many arguments (as opposed to too _few_) then |
| // we'll highlight the extraneous arguments as the diagnostic |
| // subject. |
| subjectRange = hcl.RangeBetween(call.Arguments[1].Range(), call.Arguments[len(call.Arguments)-1].Range()) |
| } |
| |
| switch call.Name { |
| case "list", "set", "map": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", call.Name), |
| Subject: &subjectRange, |
| Context: &contextRange, |
| }} |
| case "object": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.", |
| Subject: &subjectRange, |
| Context: &contextRange, |
| }} |
| case "tuple": |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "The tuple type constructor requires one argument specifying the element types as a list.", |
| Subject: &subjectRange, |
| Context: &contextRange, |
| }} |
| } |
| } |
| |
| switch call.Name { |
| |
| case "list": |
| ety, diags := getType(call.Arguments[0], constraint) |
| return cty.List(ety), diags |
| case "set": |
| ety, diags := getType(call.Arguments[0], constraint) |
| return cty.Set(ety), diags |
| case "map": |
| ety, diags := getType(call.Arguments[0], constraint) |
| return cty.Map(ety), diags |
| case "object": |
| attrDefs, diags := hcl.ExprMap(call.Arguments[0]) |
| if diags.HasErrors() { |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "Object type constructor requires a map whose keys are attribute names and whose values are the corresponding attribute types.", |
| Subject: call.Arguments[0].Range().Ptr(), |
| Context: expr.Range().Ptr(), |
| }} |
| } |
| |
| atys := make(map[string]cty.Type) |
| for _, attrDef := range attrDefs { |
| attrName := hcl.ExprAsKeyword(attrDef.Key) |
| if attrName == "" { |
| diags = append(diags, &hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "Object constructor map keys must be attribute names.", |
| Subject: attrDef.Key.Range().Ptr(), |
| Context: expr.Range().Ptr(), |
| }) |
| continue |
| } |
| aty, attrDiags := getType(attrDef.Value, constraint) |
| diags = append(diags, attrDiags...) |
| atys[attrName] = aty |
| } |
| return cty.Object(atys), diags |
| case "tuple": |
| elemDefs, diags := hcl.ExprList(call.Arguments[0]) |
| if diags.HasErrors() { |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: "Tuple type constructor requires a list of element types.", |
| Subject: call.Arguments[0].Range().Ptr(), |
| Context: expr.Range().Ptr(), |
| }} |
| } |
| etys := make([]cty.Type, len(elemDefs)) |
| for i, defExpr := range elemDefs { |
| ety, elemDiags := getType(defExpr, constraint) |
| diags = append(diags, elemDiags...) |
| etys[i] = ety |
| } |
| return cty.Tuple(etys), diags |
| default: |
| // Can't access call.Arguments in this path because we've not validated |
| // that it contains exactly one expression here. |
| return cty.DynamicPseudoType, hcl.Diagnostics{{ |
| Severity: hcl.DiagError, |
| Summary: invalidTypeSummary, |
| Detail: fmt.Sprintf("Keyword %q is not a valid type constructor.", call.Name), |
| Subject: expr.Range().Ptr(), |
| }} |
| } |
| } |