Skip to content

Commit

Permalink
Merge #11676
Browse files Browse the repository at this point in the history
11676: Pretty print PCL types in error messages r=iwahbe a=iwahbe

This PR changes how type errors are printed, adding multi-line pretty printing to types. It is part of an effort to make PCL type error messages better (#11573).

For comparison, here is the same error message with and without this PR:

Before:
```
--- FAIL: TestGenerateExamples (0.00s)
    --- FAIL: TestGenerateExamples/aws-static-website (0.19s)
        example_transpile_test.go:124: 
                Error Trace:    /Users/ianwahbe/go/src/github.com/pulumi/pulumi-yaml/pkg/tests/example_transpile_test.go:124
                Error:          Should be false
                Test:           TestGenerateExamples/aws-static-website
                Messages:       aws-static-website.pp:9,11-21: cannot assign expression of type object({accelerateConfiguration = output(union(none, object({accelerationStatus = enum(aws-native:s3:BucketAccelerateConfigurationAccelerationStatus(string): cty.StringVal("Enabled"),cty.StringVal("Suspended"))}, annotated(0xc000ad06c0)))), accessControl = output(union(enum(aws-native:s3:BucketAccessControl(string): cty.StringVal("AuthenticatedRead"),cty.StringVal("AwsExecRead"),cty.StringVal("BucketOwnerFullControl"),cty.StringVal("BucketOwnerRead"),cty.StringVal("LogDeliveryWrite"),cty.StringVal("Private"),cty.StringVal("PublicRead"),cty.StringVal("PublicReadWrite")), none)), analyticsConfigurations = output(union(list(object({id = string, prefix = union(none, string), storageClassAnalysis = object({dataExport = union(none, object({destination = object({bucketAccountId = union(none, string), bucketArn = string, format = enum(aws-native:s3:BucketDestinationFormat(string): cty.StringVal("CSV"),cty.StringVal("ORC"),cty.StringVal("Parquet")), prefix = union(none, string)}, annotated(0xc000ad0e40)), outputSchemaVersion = string}, annotated(0xc000ad1140)))}, annotated(0xc000ad1240)), tagFilters = union(list(object({key = string, value = string}, annotated(0xc000ad1400))), none)}, annotated(0xc000ad1540))), none)), arn = output(string), bucketEncryption = output(union(none, object({serverSideEncryptionConfiguration = list(object({bucketKeyEnabled = union(bool, none), serverSideEncryptionByDefault = union(none, object({kMSMasterKeyID = union(none, string), sSEAlgorithm = enum(aws-native:s3:BucketServerSideEncryptionByDefaultSSEAlgorithm(string): cty.StringVal("aws:kms"),cty.StringVal("AES256"))}, annotated(0xc000ad1a80)))}, annotated(0xc000ad1c00)))}, annotated(0xc000ad1d40)))), bucketName = output(union(none, string)), corsConfiguration = output(union(none, object({corsRules = list(object({allowedHeaders = union(list(string), none), allowedMethods = list(enum(aws-native:s3:BucketCorsRuleAllowedMethodsItem(string): cty.StringVal("GET"),cty.StringVal("PUT"),cty.StringVal("HEAD"),cty.StringVal("POST"),cty.StringVal("DELETE"))), allowedOrigins = list(string), exposedHeaders = union(list(string), none), id = union(none, string), maxAge = union(int, none)}, annotated(0xc000b3e4c0)))}, annotated(0xc000b3f180)))), domainName = output(string), dualStackDomainName = output(string), id = output(string), intelligentTieringConfigurations = output(union(list(object({id = string, prefix = union(none, string), status = enum(aws-native:s3:BucketIntelligentTieringConfigurationStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled")), tagFilters = union(list(object({key = string, value = string}, annotated(0xc000ad1400))), none), tierings = list(object({accessTier = enum(aws-native:s3:BucketTieringAccessTier(string): cty.StringVal("ARCHIVE_ACCESS"),cty.StringVal("DEEP_ARCHIVE_ACCESS")), days = int}, annotated(0xc000b3f6c0)))}, annotated(0xc000b3f7c0))), none)), inventoryConfigurations = output(union(list(object({destination = object({bucketAccountId = union(none, string), bucketArn = string, format = enum(aws-native:s3:BucketDestinationFormat(string): cty.StringVal("CSV"),cty.StringVal("ORC"),cty.StringVal("Parquet")), prefix = union(none, string)}, annotated(0xc000ad0e40)), enabled = bool, id = string, includedObjectVersions = enum(aws-native:s3:BucketInventoryConfigurationIncludedObjectVersions(string): cty.StringVal("All"),cty.StringVal("Current")), optionalFields = union(list(enum(aws-native:s3:BucketInventoryConfigurationOptionalFieldsItem(string): cty.StringVal("Size"),cty.StringVal("LastModifiedDate"),cty.StringVal("StorageClass"),cty.StringVal("ETag"),cty.StringVal("IsMultipartUploaded"),cty.StringVal("ReplicationStatus"),cty.StringVal("EncryptionStatus"),cty.StringVal("ObjectLockRetainUntilDate"),cty.StringVal("ObjectLockMode"),cty.StringVal("ObjectLockLegalHoldStatus"),cty.StringVal("IntelligentTieringAccessTier"),cty.StringVal("BucketKeyStatus"))), none), prefix = union(none, string), scheduleFrequency = enum(aws-native:s3:BucketInventoryConfigurationScheduleFrequency(string): cty.StringVal("Daily"),cty.StringVal("Weekly"))}, annotated(0xc000b3ff40))), none)), lifecycleConfiguration = output(union(none, object({rules = list(object({abortIncompleteMultipartUpload = union(none, object({daysAfterInitiation = int}, annotated(0xc000002740))), expirationDate = union(none, string), expirationInDays = union(int, none), expiredObjectDeleteMarker = union(bool, none), id = union(none, string), noncurrentVersionExpiration = union(none, object({newerNoncurrentVersions = union(int, none), noncurrentDays = int}, annotated(0xc000002dc0))), noncurrentVersionExpirationInDays = union(int, none), noncurrentVersionTransition = union(none, object({newerNoncurrentVersions = union(int, none), storageClass = enum(aws-native:s3:BucketNoncurrentVersionTransitionStorageClass(string): cty.StringVal("DEEP_ARCHIVE"),cty.StringVal("GLACIER"),cty.StringVal("GLACIER_IR"),cty.StringVal("INTELLIGENT_TIERING"),cty.StringVal("ONEZONE_IA"),cty.StringVal("STANDARD_IA")), transitionInDays = int}, annotated(0xc000a70600))), noncurrentVersionTransitions = union(list(object({newerNoncurrentVersions = union(int, none), storageClass = enum(aws-native:s3:BucketNoncurrentVersionTransitionStorageClass(string): cty.StringVal("DEEP_ARCHIVE"),cty.StringVal("GLACIER"),cty.StringVal("GLACIER_IR"),cty.StringVal("INTELLIGENT_TIERING"),cty.StringVal("ONEZONE_IA"),cty.StringVal("STANDARD_IA")), transitionInDays = int}, annotated(0xc000a70600))), none), objectSizeGreaterThan = union(none, string), objectSizeLessThan = union(none, string), prefix = union(none, string), status = enum(aws-native:s3:BucketRuleStatus(string): cty.StringVal("Enabled"),cty.StringVal("Disabled")), tagFilters = union(list(object({key = string, value = string}, annotated(0xc000ad1400))), none), transition = union(none, object({storageClass = enum(aws-native:s3:BucketTransitionStorageClass(string): cty.StringVal("DEEP_ARCHIVE"),cty.StringVal("GLACIER"),cty.StringVal("GLACIER_IR"),cty.StringVal("INTELLIGENT_TIERING"),cty.StringVal("ONEZONE_IA"),cty.StringVal("STANDARD_IA")), transitionDate = union(none, string), transitionInDays = union(int, none)}, annotated(0xc000a71780))), transitions = union(list(object({storageClass = enum(aws-native:s3:BucketTransitionStorageClass(string): cty.StringVal("DEEP_ARCHIVE"),cty.StringVal("GLACIER"),cty.StringVal("GLACIER_IR"),cty.StringVal("INTELLIGENT_TIERING"),cty.StringVal("ONEZONE_IA"),cty.StringVal("STANDARD_IA")), transitionDate = union(none, string), transitionInDays = union(int, none)}, annotated(0xc000a71780))), none)}, annotated(0xc000a71bc0)))}, annotated(0xc000e74100)))), loggingConfiguration = output(union(none, object({destinationBucketName = union(none, string), logFilePrefix = union(none, string)}, annotated(0xc000e743c0)))), metricsConfigurations = output(union(list(object({accessPointArn = union(none, string), id = string, prefix = union(none, string), tagFilters = union(list(object({key = string, value = string}, annotated(0xc000ad1400))), none)}, annotated(0xc000e74840))), none)), notificationConfiguration = output(union(none, object({eventBridgeConfiguration = union(none, object({eventBridgeEnabled = bool}, annotated(0xc000e74b80))), lambdaConfigurations = union(list(object({event = string, filter = union(none, object({s3Key = object({rules = list(object({name = string, value = string}, annotated(0xc000e74e40)))}, annotated(0xc000e74f00))}, annotated(0xc000e74f80))), function = string}, annotated(0xc000e750c0))), none), queueConfigurations = union(list(object({event = string, filter = union(none, object({s3Key = object({rules = list(object({name = string, value = string}, annotated(0xc000e74e40)))}, annotated(0xc000e74f00))}, annotated(0xc000e74f80))), queue = string}, annotated(0xc000e75440))), none), topicConfigurations = union(list(object({event = string, filter = union(none, object({s3Key = object({rules = list(object({name = string, value = string}, annotated(0xc000e74e40)))}, annotated(0xc000e74f00))}, annotated(0xc000e74f80))), topic = string}, annotated(0xc000e757c0))), none)}, annotated(0xc000e759c0)))), objectLockConfiguration = output(union(none, object({objectLockEnabled = union(none, string), rule = union(none, object({defaultRetention = union(none, object({days = union(int, none), mode = union(enum(aws-native:s3:BucketDefaultRetentionMode(string): cty.StringVal("COMPLIANCE"),cty.StringVal("GOVERNANCE")), none), years = union(int, none)}, annotated(0xc000940080)))}, annotated(0xc000940300)))}, annotated(0xc000940440)))), objectLockEnabled = output(union(bool, none)), ownershipControls = output(union(none, object({rules = list(object({objectOwnership = union(enum(aws-native:s3:BucketOwnershipControlsRuleObjectOwnership(string): cty.StringVal("ObjectWriter"),cty.StringVal("BucketOwnerPreferred"),cty.StringVal("BucketOwnerEnforced")), none)}, annotated(0xc000940840)))}, annotated(0xc000940980)))), publicAccessBlockConfiguration = output(union(none, object({blockPublicAcls = union(bool, none), blockPublicPolicy = union(bool, none), ignorePublicAcls = union(bool, none), restrictPublicBuckets = union(bool, none)}, annotated(0xc000940dc0)))), regionalDomainName = output(string), replicationConfiguration = output(union(none, object({role = string, rules = list(object({deleteMarkerReplication = union(none, object({status = union(enum(aws-native:s3:BucketDeleteMarkerReplicationStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled")), none)}, annotated(0xc000941280))), destination = object({accessControlTranslation = union(none, object({owner = string}, annotated(0xc0009414c0))), account = union(none, string), bucket = string, encryptionConfiguration = union(none, object({replicaKmsKeyID = string}, annotated(0xc000941740))), metrics = union(none, object({eventThreshold = union(none, object({minutes = int}, annotated(0xc000941900))), status = enum(aws-native:s3:BucketMetricsStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled"))}, annotated(0xc000941ac0))), replicationTime = union(none, object({status = enum(aws-native:s3:BucketReplicationTimeStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled")), time = object({minutes = int}, annotated(0xc000941900))}, annotated(0xc000941d80))), storageClass = union(enum(aws-native:s3:BucketReplicationDestinationStorageClass(string): cty.StringVal("DEEP_ARCHIVE"),cty.StringVal("GLACIER"),cty.StringVal("GLACIER_IR"),cty.StringVal("INTELLIGENT_TIERING"),cty.StringVal("ONEZONE_IA"),cty.StringVal("REDUCED_REDUNDANCY"),cty.StringVal("STANDARD"),cty.StringVal("STANDARD_IA")), none)}, annotated(0xc0009a0000)), filter = union(none, object({and = union(none, object({prefix = union(none, string), tagFilters = union(list(object({key = string, value = string}, annotated(0xc000ad1400))), none)}, annotated(0xc0009a0500))), prefix = union(none, string), tagFilter = union(none, object({key = string, value = string}, annotated(0xc000ad1400)))}, annotated(0xc0009a0800))), id = union(none, string), prefix = union(none, string), priority = union(int, none), sourceSelectionCriteria = union(none, object({replicaModifications = union(none, object({status = enum(aws-native:s3:BucketReplicaModificationsStatus(string): cty.StringVal("Enabled"),cty.StringVal("Disabled"))}, annotated(0xc0009a0dc0))), sseKmsEncryptedObjects = union(none, object({status = enum(aws-native:s3:BucketSseKmsEncryptedObjectsStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled"))}, annotated(0xc0009a1000)))}, annotated(0xc0009a1140))), status = enum(aws-native:s3:BucketReplicationRuleStatus(string): cty.StringVal("Disabled"),cty.StringVal("Enabled"))}, annotated(0xc0009a1380)))}, annotated(0xc0009a1680)))), tags = output(union(list(object({key = string, value = string}, annotated(0xc0009a1840))), none)), urn = output(string), versioningConfiguration = output(union(none, object({status = enum(aws-native:s3:BucketVersioningConfigurationStatus(string): cty.StringVal("Enabled"),cty.StringVal("Suspended"))}, annotated(0xc0009a1a80)))), websiteConfiguration = output(union(none, object({errorDocument = union(none, string), indexDocument = union(none, string), redirectAllRequestsTo = union(none, object({hostName = string, protocol = union(enum(aws-native:s3:BucketRedirectAllRequestsToProtocol(string): cty.StringVal("http"),cty.StringVal("https")), none)}, annotated(0xc0009a1f40))), routingRules = union(list(object({redirectRule = object({hostName = union(none, string), httpRedirectCode = union(none, string), protocol = union(enum(aws-native:s3:BucketRedirectRuleProtocol(string): cty.StringVal("http"),cty.StringVal("https")), none), replaceKeyPrefixWith = union(none, string), replaceKeyWith = union(none, string)}, annotated(0xc000a24580)), routingRuleCondition = union(none, object({httpErrorCodeReturnedEquals = union(none, string), keyPrefixEquals = union(none, string)}, annotated(0xc000a249c0)))}, annotated(0xc000a24b40))), none)}, annotated(0xc000a24cc0)))), websiteURL = output(string)}, annotated(0xc000a25480)) to location of type union(output(string), output(union(string, type(aws:s3/bucket:Bucket))), string, type(aws:s3/bucket:Bucket), annotated(0xc000a25a80)): ; , and 1 other diagnostic(s)
FAIL
FAIL    github.com/pulumi/pulumi-yaml/pkg/tests 1.511s
?       github.com/pulumi/pulumi-yaml/pkg/version       [no test files]
FAIL
```

After:
```
--- FAIL: TestGenerateExamples (0.00s)
    --- FAIL: TestGenerateExamples/aws-static-website (1.88s)
        example_transpile_test.go:124: 
                Error Trace:    /Users/ianwahbe/go/src/github.com/pulumi/pulumi-yaml/pkg/tests/example_transpile_test.go:124
                Error:          Should be false
                Test:           TestGenerateExamples/aws-static-website
                Messages:       aws-static-website.pp:9,11-21: cannot assign expression of type {
                                  accelerateConfiguration: output({ accelerationStatus: enum("Enabled" | "Suspended") }?),
                                  accessControl: output(enum("AuthenticatedRead"
                                    | "AwsExecRead"
                                    | "BucketOwnerFullControl"
                                    | "BucketOwnerRead"
                                    | "LogDeliveryWrite"
                                    | "Private"
                                    | "PublicRead"
                                    | "PublicReadWrite")?),
                                  analyticsConfigurations: output(list({
                                      id: string,
                                      prefix: string?,
                                      storageClassAnalysis: {
                                        dataExport: {
                                            destination: {
                                              bucketAccountId: string?,
                                              bucketArn: string,
                                              format: enum("CSV" | "ORC" | "Parquet"),
                                              prefix: string?,
                                            },
                                            outputSchemaVersion: string,
                                          }?,
                                      },
                                      tagFilters: list({ key: string, value: string })?,
                                    })?),
                                  arn: output(string),
                                  bucketEncryption: output({
                                      serverSideEncryptionConfiguration: list({
                                        bucketKeyEnabled: bool?,
                                        serverSideEncryptionByDefault: { kMSMasterKeyID: string?, sSEAlgorithm: enum("aws:kms" | "AES256") }?,
                                      }),
                                    }?),
                                  bucketName: output(string?),
                                  corsConfiguration: output({
                                      corsRules: list({
                                        allowedHeaders: list(string)?,
                                        allowedMethods: list(enum("GET" | "PUT" | "HEAD" | "POST" | "DELETE")),
                                        allowedOrigins: list(string),
                                        exposedHeaders: list(string)?,
                                        id: string?,
                                        maxAge: int?,
                                      }),
                                    }?),
                                  domainName: output(string),
                                  dualStackDomainName: output(string),
                                  id: output(string),
                                  intelligentTieringConfigurations: output(list({
                                      id: string,
                                      prefix: string?,
                                      status: enum("Disabled" | "Enabled"),
                                      tagFilters: list({ key: string, value: string })?,
                                      tierings: list({ accessTier: enum("ARCHIVE_ACCESS" | "DEEP_ARCHIVE_ACCESS"), days: int }),
                                    })?),
                                  inventoryConfigurations: output(list({
                                      destination: {
                                        bucketAccountId: string?,
                                        bucketArn: string,
                                        format: enum("CSV" | "ORC" | "Parquet"),
                                        prefix: string?,
                                      },
                                      enabled: bool,
                                      id: string,
                                      includedObjectVersions: enum("All" | "Current"),
                                      optionalFields: list(enum("Size"
                                        | "LastModifiedDate"
                                        | "StorageClass"
                                        | "ETag"
                                        | "IsMultipartUploaded"
                                        | "ReplicationStatus"
                                        | "EncryptionStatus"
                                        | "ObjectLockRetainUntilDate"
                                        | "ObjectLockMode"
                                        | "ObjectLockLegalHoldStatus"
                                        | "IntelligentTieringAccessTier"
                                        | "BucketKeyStatus"))?,
                                      prefix: string?,
                                      scheduleFrequency: enum("Daily" | "Weekly"),
                                    })?),
                                  lifecycleConfiguration: output({
                                      rules: list({
                                        abortIncompleteMultipartUpload: { daysAfterInitiation: int }?,
                                        expirationDate: string?,
                                        expirationInDays: int?,
                                        expiredObjectDeleteMarker: bool?,
                                        id: string?,
                                        noncurrentVersionExpiration: { newerNoncurrentVersions: int?, noncurrentDays: int }?,
                                        noncurrentVersionExpirationInDays: int?,
                                        noncurrentVersionTransition: {
                                            newerNoncurrentVersions: int?,
                                            storageClass: enum("DEEP_ARCHIVE"
                                            | "GLACIER"
                                            | "GLACIER_IR"
                                            | "INTELLIGENT_TIERING"
                                            | "ONEZONE_IA"
                                            | "STANDARD_IA"),
                                            transitionInDays: int,
                                          }?,
                                        noncurrentVersionTransitions: list({
                                            newerNoncurrentVersions: int?,
                                            storageClass: enum("DEEP_ARCHIVE"
                                            | "GLACIER"
                                            | "GLACIER_IR"
                                            | "INTELLIGENT_TIERING"
                                            | "ONEZONE_IA"
                                            | "STANDARD_IA"),
                                            transitionInDays: int,
                                          })?,
                                        objectSizeGreaterThan: string?,
                                        objectSizeLessThan: string?,
                                        prefix: string?,
                                        status: enum("Enabled" | "Disabled"),
                                        tagFilters: list({ key: string, value: string })?,
                                        transition: {
                                            storageClass: enum("DEEP_ARCHIVE"
                                            | "GLACIER"
                                            | "GLACIER_IR"
                                            | "INTELLIGENT_TIERING"
                                            | "ONEZONE_IA"
                                            | "STANDARD_IA"),
                                            transitionDate: string?,
                                            transitionInDays: int?,
                                          }?,
                                        transitions: list({
                                            storageClass: enum("DEEP_ARCHIVE"
                                            | "GLACIER"
                                            | "GLACIER_IR"
                                            | "INTELLIGENT_TIERING"
                                            | "ONEZONE_IA"
                                            | "STANDARD_IA"),
                                            transitionDate: string?,
                                            transitionInDays: int?,
                                          })?,
                                      }),
                                    }?),
                                  loggingConfiguration: output({ destinationBucketName: string?, logFilePrefix: string? }?),
                                  metricsConfigurations: output(list({
                                      accessPointArn: string?,
                                      id: string,
                                      prefix: string?,
                                      tagFilters: list({ key: string, value: string })?,
                                    })?),
                                  notificationConfiguration: output({
                                      eventBridgeConfiguration: { eventBridgeEnabled: bool }?,
                                      lambdaConfigurations: list({
                                          event: string,
                                          filter: { s3Key: { rules: list({ name: string, value: string }) } }?,
                                          function: string,
                                        })?,
                                      queueConfigurations: list({
                                          event: string,
                                          filter: { s3Key: { rules: list({ name: string, value: string }) } }?,
                                          queue: string,
                                        })?,
                                      topicConfigurations: list({
                                          event: string,
                                          filter: { s3Key: { rules: list({ name: string, value: string }) } }?,
                                          topic: string,
                                        })?,
                                    }?),
                                  objectLockConfiguration: output({
                                      objectLockEnabled: string?,
                                      rule: { defaultRetention: { days: int?, mode: enum("COMPLIANCE" | "GOVERNANCE")?, years: int? }? }?,
                                    }?),
                                  objectLockEnabled: output(bool?),
                                  ownershipControls: output({
                                      rules: list({ objectOwnership: enum("ObjectWriter" | "BucketOwnerPreferred" | "BucketOwnerEnforced")? }),
                                    }?),
                                  publicAccessBlockConfiguration: output({
                                      blockPublicAcls: bool?,
                                      blockPublicPolicy: bool?,
                                      ignorePublicAcls: bool?,
                                      restrictPublicBuckets: bool?,
                                    }?),
                                  regionalDomainName: output(string),
                                  replicationConfiguration: output({
                                      role: string,
                                      rules: list({
                                        deleteMarkerReplication: { status: enum("Disabled" | "Enabled")? }?,
                                        destination: {
                                          accessControlTranslation: { owner: string }?,
                                          account: string?,
                                          bucket: string,
                                          encryptionConfiguration: { replicaKmsKeyID: string }?,
                                          metrics: { eventThreshold: { minutes: int }?, status: enum("Disabled" | "Enabled") }?,
                                          replicationTime: { status: enum("Disabled" | "Enabled"), time: { minutes: int } }?,
                                          storageClass: enum("DEEP_ARCHIVE"
                                            | "GLACIER"
                                            | "GLACIER_IR"
                                            | "INTELLIGENT_TIERING"
                                            | "ONEZONE_IA"
                                            | "REDUCED_REDUNDANCY"
                                            | "STANDARD"
                                            | "STANDARD_IA")?,
                                        },
                                        filter: {
                                            and: { prefix: string?, tagFilters: list({ key: string, value: string })? }?,
                                            prefix: string?,
                                            tagFilter: { key: string, value: string }?,
                                          }?,
                                        id: string?,
                                        prefix: string?,
                                        priority: int?,
                                        sourceSelectionCriteria: {
                                            replicaModifications: { status: enum("Enabled" | "Disabled") }?,
                                            sseKmsEncryptedObjects: { status: enum("Disabled" | "Enabled") }?,
                                          }?,
                                        status: enum("Disabled" | "Enabled"),
                                      }),
                                    }?),
                                  tags: output(list({ key: string, value: string })?),
                                  urn: output(string),
                                  versioningConfiguration: output({ status: enum("Enabled" | "Suspended") }?),
                                  websiteConfiguration: output({
                                      errorDocument: string?,
                                      indexDocument: string?,
                                      redirectAllRequestsTo: { hostName: string, protocol: enum("http" | "https")? }?,
                                      routingRules: list({
                                          redirectRule: {
                                            hostName: string?,
                                            httpRedirectCode: string?,
                                            protocol: enum("http" | "https")?,
                                            replaceKeyPrefixWith: string?,
                                            replaceKeyWith: string?,
                                          },
                                          routingRuleCondition: 
                                            { httpErrorCodeReturnedEquals: string?, keyPrefixEquals: string? }?,
                                        })?,
                                    }?),
                                  websiteURL: output(string),
                                } to location of type output(string) | output(string | type(aws:s3/bucket:Bucket)) | string | type(aws:s3/bucket:Bucket): ; , and 1 other diagnostic(s)
FAIL
FAIL    github.com/pulumi/pulumi-yaml/pkg/tests 2.401s
?       github.com/pulumi/pulumi-yaml/pkg/version       [no test files]
FAIL
```

Co-authored-by: Ian Wahbe <ian@wahbe.com>
  • Loading branch information
bors[bot] and iwahbe committed Dec 19, 2022
2 parents d488560 + 9109607 commit 8776498
Show file tree
Hide file tree
Showing 16 changed files with 705 additions and 11 deletions.
8 changes: 4 additions & 4 deletions pkg/codegen/hcl2/model/diagnostics.go
Expand Up @@ -44,13 +44,13 @@ func ExprNotConvertible(destType Type, expr Expression) *hcl.Diagnostic {
if len(why) != 0 {
return errorf(expr.SyntaxNode().Range(), why[0].Summary)
}
return errorf(expr.SyntaxNode().Range(), "cannot assign expression of type %v to location of type %v: ", expr.Type(),
destType)
return errorf(expr.SyntaxNode().Range(), "cannot assign expression of type %s to location of type %s: ",
expr.Type().Pretty(), destType.Pretty())
}

func typeNotConvertible(dest, src Type) *hcl.Diagnostic {
return &hcl.Diagnostic{Severity: hcl.DiagError, Summary: fmt.Sprintf("cannot assign value of type %v to type %v",
src, dest)}
return &hcl.Diagnostic{Severity: hcl.DiagError, Summary: fmt.Sprintf("cannot assign value of type %s to type %s",
src.Pretty(), dest.Pretty())}
}

func tuplesHaveDifferentLengths(dest, src *TupleType) *hcl.Diagnostic {
Expand Down
337 changes: 337 additions & 0 deletions pkg/codegen/hcl2/model/pretty/display.go
@@ -0,0 +1,337 @@
// Copyright 2016-2022, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// pretty is an extensible utility library to pretty-print nested structures.
package pretty

import (
"fmt"
"sort"
"strings"
)

const (
DefaultColumns int = 100
DefaultIndent string = " "
)

// A formatter understands how to turn itself into a string while respecting a desired
// column target.
type Formatter interface {
fmt.Stringer

// Set the number of columns to print out.
// This method does not mutate.
Columns(int) Formatter
}

// Indent a (multi-line) string, passing on column adjustments.
type indent struct {
// The prefix to be applied to each line
prefix string
inner Formatter
}

func sanitizeColumns(i int) int {
if i <= 0 {
return 1
}
return i
}

func (i indent) Columns(columns int) Formatter {
i.inner = i.inner.Columns(columns - len(i.prefix))
return i
}

func (i indent) String() string {
lines := strings.Split(i.inner.String(), "\n")
for j, l := range lines {
lines[j] = i.prefix + l
}
return strings.Join(lines, "\n")
}

// Create a new Formatter of a raw string.
func FromString(s string) Formatter {
return &literal{s: s}
}

// Create a new Formatter from a fmt.Stringer.
func FromStringer(s fmt.Stringer) Formatter {
return &literal{t: s}
}

// A string literal that implements Formatter (ignoring Column).
type literal struct {
// A source for a string.
t fmt.Stringer
// A raw string.
s string
}

func (b *literal) String() string {
// If we don't have a cached value, but we can compute one,
if b.s == "" && b.t != nil {
// Set the known value to the computed value
b.s = b.t.String()
// Nil the value source, since we won't need it again, and it might produce "".
b.t = nil
}
return b.s
}

func (b literal) Columns(int) Formatter {
// We are just calling .String() here, so we can't do anything with columns.
return &b
}

// A Formatter that wraps an inner value with prefixes and postfixes.
//
// Wrap attempts to respect its column target by changing if the prefix and postfix are on
// the same line as the inner value, or their own lines.
//
// As an example, consider the following instance of Wrap:
//
// Wrap {
// Prefix: "number(", Postfix: ")"
// Value: FromString("123456")
// }
//
// It could be rendered as
//
// number(123456)
//
// or
//
// number(
// 123456
// )
//
// depending on the column constrains.
type Wrap struct {
Prefix, Postfix string
// Require that the Postfix is always on the same line as Value.
PostfixSameline bool
Value Formatter

columns int
}

func (w Wrap) String() string {
columns := w.columns
if columns == 0 {
columns = DefaultColumns
}
inner := w.Value.Columns(columns - len(w.Prefix) - len(w.Postfix)).String()
lines := strings.Split(inner, "\n")

if len(lines) == 1 {
// Full result on one line, and it fits
if len(inner)+len(w.Prefix)+len(w.Postfix) < columns ||
// Or its more efficient to include the wrapping instead of the indent.
len(w.Prefix)+len(w.Postfix) < len(DefaultIndent) {
return w.Prefix + inner + w.Postfix
}

// Print the prefix and postix on their own line, then indent the inner value.
pre := w.Prefix
if pre != "" {
pre += "\n"
}
post := w.Postfix
if post != "" && !w.PostfixSameline {
post = "\n" + post
}
if w.PostfixSameline {
columns -= len(w.Postfix)
}
return pre + indent{
prefix: DefaultIndent,
inner: w.Value,
}.Columns(columns).String() + post
}

// See if we can afford to wrap the prefix & postfix around the first and last lines.
separate := (w.Prefix != "" && len(w.Prefix)+len(lines[0]) >= columns) ||
(w.Postfix != "" && len(w.Postfix)+len(lines[len(lines)-1]) >= columns && !w.PostfixSameline)

if !separate {
return w.Prefix + strings.TrimSpace(w.Value.Columns(columns).String()) + w.Postfix
}
s := w.Prefix
if w.Prefix != "" {
s += "\n"
}
s += indent{
prefix: DefaultIndent,
inner: w.Value,
}.Columns(columns).String()
if w.Postfix != "" && !w.PostfixSameline {
s += "\n" + w.Postfix
}
return s
}

func (w Wrap) Columns(columns int) Formatter {
w.columns = sanitizeColumns(columns)
return w
}

// Object is a Formatter that prints string-Formatter pairs, respecting columns where
// possible.
//
// It does this by deciding if the object should be compressed into a single line, or have
// one field per line.
type Object struct {
Properties map[string]Formatter
columns int
}

func (o Object) String() string {
if len(o.Properties) == 0 {
return "{}"
}
columns := o.columns
if columns <= 0 {
columns = DefaultColumns
}

// Check if we can do the whole object in a single line
keys := make([]string, 0, len(o.Properties))
for key := range o.Properties {
keys = append(keys, key)
}
sort.StringSlice(keys).Sort()

// Try to build the object in a single line
singleLine := true
s := "{ "
overflowing := func() bool {
return columns < len(s)-1
}
for i, key := range keys {
s += key + ": "
v := o.Properties[key].Columns(columns - len(s) - 1).String()
if strings.IndexRune(v, '\n') != -1 {
singleLine = false
break
}
if i+1 < len(keys) {
v += ","
}
s += v + " "

if overflowing() {
// The object is too big for a single line. Give up and create a multi-line
// object.
singleLine = false
break
}
}
if singleLine {
return s + "}"
}

// reset for a mutl-line object.
s = "{\n"
for _, key := range keys {
s += indent{
prefix: DefaultIndent,
inner: Wrap{
Prefix: key + ": ",
Postfix: ",",
PostfixSameline: true,
Value: o.Properties[key],
},
}.Columns(columns).String() + "\n"
}
return s + "}"
}

func (o Object) Columns(columns int) Formatter {
o.columns = sanitizeColumns(columns)
return o
}

// An ordered set of items displayed with a separator between them.
//
// Items can be displayed on a single line if it fits within the column constraint.
// Otherwise items will be displayed across multiple lines.
type List struct {
Elements []Formatter
Separator string
AdjoinSeparator bool

columns int
}

func (l List) String() string {
columns := l.columns
if columns <= 0 {
columns = DefaultColumns
}
s := ""
singleLine := true
for i, el := range l.Elements {
v := el.Columns(columns - len(s)).String()
if strings.IndexRune(v, '\n') != -1 {
singleLine = false
break
}
s += v
if i+1 < len(l.Elements) {
s += l.Separator
}

if len(s) > columns {
singleLine = false
break
}
}
if singleLine {
return s
}
s = ""
if l.AdjoinSeparator {
separator := strings.TrimRight(l.Separator, " ")
for i, el := range l.Elements {
v := el.Columns(columns - len(separator)).String()
if i+1 != len(l.Elements) {
v += separator + "\n"
}
s += v
}
return s
}

separator := strings.TrimLeft(l.Separator, " ")
for i, el := range l.Elements {
v := indent{
prefix: strings.Repeat(" ", len(separator)),
inner: el,
}.Columns(columns).String()
if i != 0 {
v = "\n" + separator + v[len(separator):]
}
s += v
}
return s

}

func (l List) Columns(columns int) Formatter {
l.columns = sanitizeColumns(columns)
return l
}

0 comments on commit 8776498

Please sign in to comment.