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

Short-circuit behavior for the && and || operators #538

Closed
wants to merge 3 commits into from

Commits on Jun 14, 2022

  1. go.mod: Specify that we use the Go 1.17 language in this module

    The main motivation here is that we were apparently using bit shifting
    with signed shift amounts even though modern Go toolchains seem to raise
    that as an error if the go.mod file selects a language version less than
    Go 1.13.
    
    Go 1.17 is therefore newer than we strictly need to solve that particular
    problem, but gets us caught up with a relatively release of the language.
    This alone doesn't break compatibility for anyone using older versions of
    Go with HCL, since the Go toolchain will still attempt to compile modules
    targeting later versions and will only mention the newer language version
    if compilation would already have failed for some other reason.
    apparentlymart committed Jun 14, 2022
    Configuration menu
    Copy the full SHA
    b440b17 View commit details
    Browse the repository at this point in the history
  2. hclsyntax: && and || operators are right-associative

    Previously we just treated all binary operators as being left-associative,
    which for many of them didn't make a lot of difference anyway because they
    were associative operators.
    
    The logical AND and OR operators are also effectively associative the way
    we have them implemented right now, because HCL always evaluates both
    sides of a binary operator anyway and so there's no way to depend on
    the associativity for either of these operations.
    
    However, we'd like to implement a similar short-circuit behavior as we see
    in many other similar languages, at which point the evaluation order of
    the operations would become important in order to constrain which
    expressions get fully evaluated and which are "short-circuited". Treating
    these operators as right-associative instead of left-associative will
    make the subsequent implementation more intuitive, since the LHS of the
    outermost operation will decide whether to evaluate the RHS, rather than
    recursing into the LHS completely first and then unwinding if the innermost
    term causes the short-circuit.
    
    Since we expect HCL expression evaluation to always be side-effect-free
    it doesn't technically matter what associativity we use -- the result would
    be the same either way -- but this approach will make the control flow
    during evaluation match intuition about how a short-circuit behaves.
    
    This commit does not yet actually implement the short-circuit behavior,
    which will hopefully follow in a subsequent commit.
    apparentlymart committed Jun 14, 2022
    Configuration menu
    Copy the full SHA
    9ba6757 View commit details
    Browse the repository at this point in the history
  3. hclsyntax: Short-circuit behavior for the && and || operators

    Previously we just always evaluated both operands of any binary operator
    and then executed the operator. For the logical operators that was
    inconsistent with their treatment in several other languages with a
    similar grammar to ours, leading to users being surprised that HCL didn't
    short circuit the same way as their favorite other languages did.
    
    Our initial exclusion of short-circuiting here was, as with various other
    parts of HCL's design, motivated by the goals of producing consistent
    results even when unknown values are present and of proactively returning
    as many errors as possible in order to give better context when debugging.
    
    However, in acknowledgement of the fact that logical operator
    short-circuiting has considerable ergonomic benefits when writing out
    compound conditional expressions where later terms rely on the guarantees
    of earlier terms, this commit implements a compromise design where we
    can get the short-circuit benefits for dynamic-value-related errors
    without losing our ability to proactively detect type-related errors even
    when short-circuiting.
    
    Specifically, if a particular operator participates in short-circuiting
    then it gets an opportunity to decide for a particular known LHS value
    whether to short-circuit. If it decides to do so then HCL will evaluate
    the RHS in a type-checking-only mode, where we ignore any specific values
    in the variable scope but will still raise errors if the RHS expression
    tries to do anything to them that is inconsistent with their type.
    
    If the LHS of a short-circuit-capable operator turns out to be an unknown
    value of a suitable type, we'll pessimistically treat it as a short-circuit
    and defer full evaluation of the RHS until we have more information, so
    as to avoid raising errors that would be guarded away once the LHS becomes
    known.
    
    The effect of this compromise is that it's possible to use the
    short-circuit operators in common value-related guard situations, like
    checking whether a value is null or is a number that would be valid
    to index a particular list:
        foo != null && foo.something
        idx < 3 && foo[idx]
    
    On the other hand though, it is _not_ possible to use the behavior to
    guard against problems that are related to types rather than to values,
    which allows us to still proactively detect various kinds of errors that
    are likely to be mistakes:
        foo != null && fo.something   # Typoed "foo" as "fo"
        foo != null && foo.smething   # Typoed "something" as "smething"
        num < 3 && num.something      # Numbers never have attributes
    
    Those coming from dynamic languages will probably still find these errors
    surprising, but HCL's other features follow a similar sort of hybrid
    model of statically checking types where possible and that tradeoff seems
    to have worked well to make it possible to express more complicated
    situations while still providing some of the help typically expected from
    a static type system for "obviously wrong" situations. It should typically
    be possible to adjust an expression to make it friendly to short-circuit
    guard style by being more precise about the types being used.
    apparentlymart committed Jun 14, 2022
    Configuration menu
    Copy the full SHA
    4790ab6 View commit details
    Browse the repository at this point in the history