Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blocked] Check unique default before using the value #26

Closed
wants to merge 3 commits into from
Closed

Conversation

ying-jeanne
Copy link
Collaborator

@ying-jeanne ying-jeanne commented Aug 31, 2021

fixes #15

@ying-jeanne ying-jeanne force-pushed the 15 branch 2 times, most recently from 7fb382d to e5fddcb Compare August 31, 2021 09:57
@ying-jeanne ying-jeanne changed the title check default before return Check unique default before using the value Aug 31, 2021
@ying-jeanne ying-jeanne marked this pull request as ready for review August 31, 2021 10:01
@ying-jeanne ying-jeanne requested review from sdboyer and sh0rez August 31, 2021 10:01
@sdboyer
Copy link
Contributor

sdboyer commented Aug 31, 2021

nit: please use keywords - e.g. "fixes #15" - so that the merging of the PR will auto-close the issue. Or, if it doesn't completely address the issue and therefore shouldn't close it, just a "relates to #15" is good. Having the bare issue number is a bit ambiguous.

Copy link
Contributor

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test case to ensure that when multiple defaults arise as a result of unification, this logic still catches it. (It seems unlikely that anyone adds multiple defaults on purpose, so this is actually the case that's most important to check for.)

encoder/tests/conflictEnumDeferror Outdated Show resolved Hide resolved
encoder/tests/conflictInterfaceDeferror Outdated Show resolved Hide resolved
encoder/tests/conflictTypeDeferror Outdated Show resolved Hide resolved
@ying-jeanne
Copy link
Collaborator Author

Let's add a test case to ensure that when multiple defaults arise as a result of unification, this logic still catches it. (It seems unlikely that anyone adds multiple defaults on purpose, so this is actually the case that's most important to check for.)

I can write some example as I understand, it would be nice if we can define a use case together for the real issue, It is probably nice to define use cases when write a user story, TDD would save us some time :).



-- err --
CType has multiple defaults which is not allowed
Copy link
Collaborator Author

@ying-jeanne ying-jeanne Sep 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is making sense and erring out now

@ying-jeanne
Copy link
Collaborator Author

currently

AType: *4 | int @cuetsy(kind="type")
Foo: {
  Baz: AType | *7
} @cuetsy(kind="interface")

doesn't error but

AType: *4 | int @cuetsy(kind="type")
BType: *7 | int @cuetsy(kind="type")
Foo: {
  Baz: AType | BType
} @cuetsy(kind="interface")

error out correctly, a tad confusing

@ying-jeanne
Copy link
Collaborator Author

the snippet for the use cases: https://github.com/ying-jeanne/cue_test_cases/blob/bf84db6edba61d3479838967d7445f1775393fc3/main.go

a ticket opened on cue-lang because the expr() behaves weirdly cue-lang/cue#1245

@ying-jeanne ying-jeanne changed the title Check unique default before using the value [WIP] Check unique default before using the value Sep 2, 2021
@ying-jeanne ying-jeanne changed the title [WIP] Check unique default before using the value [Blocked] Check unique default before using the value Sep 2, 2021
@@ -0,0 +1,19 @@

This would be the typical cases we should fail, since Bar is refereced to a type with default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wording nit:

Suggested change
This would be the typical cases we should fail, since Bar is refereced to a type with default
This would be the typical cases we should fail, since Bar references a type with a default

label, _ := v.Label()
op, dvals := def.Expr()
if len(dvals) > 1 && op == cue.OrOp {
return def, true, valError(v, "%s has multiple defaults which is not allowed", label)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should print out the defaults, to improve failure locality (the cost of mapping a failure to a particular place in code). Ideally, it'd even emit line numbers, like native CUE errors do, but that's more than we need to worry about right now.

if ops == cue.OrOp {
for _, vval := range vvals {
if inst, path := vval.Reference(); len(path) > 0 {
fmt.Println(">>>>>>>>>>>>>>>>> I am here >>>>>>>>>>>>>>")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debug output needs removal

for _, def := range defs[1:] {
if !defs[0].Equals(def) {
label, _ := v.Label()
return def, true, valError(v, "%s has multiple defaults which is not allowed", label)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as other comment, print out the conflicting default values in the error message

Seems CUE is picking the default before eval it, it seems we can get the reference first and
check whether default exist there, if yes if the default also exist for the current cue object error out

Or should we just forbidden the disjunction between Enum and other types??
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really interesting question! It should probably be a comment in the PR, or even its own issue, rather than hiding it in an actual committed file, i think.

My intuition is that, first and foremost, we have to follow the constraints imposed by TypeScript's own rules. From there, we should do the least magical thing possible. Given that, my initial thought is to allow this, as:

export enum AOrEnum: {
  Foo: 'foo',
  Bar: 'bar',
  Baz: 'baz'
}

export interface Foo {
  Bar: AOrEnum | "ohai"
}

export const defaultFoo: Foo = {
  Bar: "ohai"
}

At first, this seemed reasonable, because of what i felt i could infer about authorial intent:

  1. There's an author of AOrEnum, who expressed the intent that the default value of their type be baz
  2. There's an author of Foo, who is (clearly) directly aware of AOrEnum, as they wrote some text explicitly referencing it.
  3. Foo's author, being ostensibly aware of AOrEnum, is choosing to supersede the default of baz with ohai.

However, going through this exercise led me to realize that this case is narrow and circumstantial, not based on generalizable principles of the sort people can reason about. That's because we're bumping into fundamental differences in the models on which CUE and TS are based:

  1. Defaults are a native concept in CUE that authors must deal with; no such concept exists in TS
  2. The primitive kinds in the languages are simply different (disjunction semantics in CUE; existence of enums in TS)
  3. While both TS and CUE are mostly structurally typed, which generally it possible to flow types through the system more freely than if names must strictly align, we're (necessarily) making that harder by trying to snapshot bits of the system to translate from CUE to TS, without ever being able to guarantee that we have the whole type graph to examine for completeness or correctness

i don't know what our good answers are here, yet, but the above sort of reasoning makes me reasonably sure there simply aren't any, and we shouldn't waste time or effort searching for them. Instead, we should be very conservative in what we support, erroring aggressively when the slightest bit of ambiguity arises about how to e.g. handle a default or flow an enum into an interface, and then consider opening up further when use cases present themselves.

That's still not a clear, categorical rule we can apply easily, but...well, it's a general basis for saying no and constraining scope, which is usually a safe place to be.

Copy link
Contributor

@sdboyer sdboyer Sep 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All that said, it's worth noting that CUE seems to follow the same logic about user intent/awareness that i originally ran with - see this playground, and note that C has a single default, but D does not. Which is...actually really interesting, and potentially useful.

@ying-jeanne ying-jeanne marked this pull request as draft September 16, 2021 14:04
@sdboyer sdboyer closed this May 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error out when attempting to generate a default for a disjunction with multiple defaults
2 participants