How to check that a function always return a value (aka “doesn't fall off the end”)?












3















I'm building a didactic compiler, and I'd like to check if the function will always return a value. I intend to do this in the semantic analysis step (as this is not covered by the language grammar).



Out of all the flow control statements, this didactic language only has if, else, and while statements (so no do while, for, switch cases, etc). Note that else if is also possible. The following are all valid example snippets:



a)



if (condition) {
// non-returning commands
}
return value


b)



if (condition) {
return value
}
return anotherValue


c)



if (condition) {
return value1
} else {
return value2
}
// No return value needed here


I've searched a lot about this but couldn't find a pseudoalgorithm that I could comprehend. I've searched for software path testing, the white box testing, and also other related Stack Overflow questions like this and this.



I've heard that this can be solved using graphs, and also using a stack, but I have no idea how to implement those strategies.



Any help with pseudocode would be very helpful!



(and if it matters, I'm implementing my compiler in Swift)










share|improve this question





























    3















    I'm building a didactic compiler, and I'd like to check if the function will always return a value. I intend to do this in the semantic analysis step (as this is not covered by the language grammar).



    Out of all the flow control statements, this didactic language only has if, else, and while statements (so no do while, for, switch cases, etc). Note that else if is also possible. The following are all valid example snippets:



    a)



    if (condition) {
    // non-returning commands
    }
    return value


    b)



    if (condition) {
    return value
    }
    return anotherValue


    c)



    if (condition) {
    return value1
    } else {
    return value2
    }
    // No return value needed here


    I've searched a lot about this but couldn't find a pseudoalgorithm that I could comprehend. I've searched for software path testing, the white box testing, and also other related Stack Overflow questions like this and this.



    I've heard that this can be solved using graphs, and also using a stack, but I have no idea how to implement those strategies.



    Any help with pseudocode would be very helpful!



    (and if it matters, I'm implementing my compiler in Swift)










    share|improve this question



























      3












      3








      3








      I'm building a didactic compiler, and I'd like to check if the function will always return a value. I intend to do this in the semantic analysis step (as this is not covered by the language grammar).



      Out of all the flow control statements, this didactic language only has if, else, and while statements (so no do while, for, switch cases, etc). Note that else if is also possible. The following are all valid example snippets:



      a)



      if (condition) {
      // non-returning commands
      }
      return value


      b)



      if (condition) {
      return value
      }
      return anotherValue


      c)



      if (condition) {
      return value1
      } else {
      return value2
      }
      // No return value needed here


      I've searched a lot about this but couldn't find a pseudoalgorithm that I could comprehend. I've searched for software path testing, the white box testing, and also other related Stack Overflow questions like this and this.



      I've heard that this can be solved using graphs, and also using a stack, but I have no idea how to implement those strategies.



      Any help with pseudocode would be very helpful!



      (and if it matters, I'm implementing my compiler in Swift)










      share|improve this question
















      I'm building a didactic compiler, and I'd like to check if the function will always return a value. I intend to do this in the semantic analysis step (as this is not covered by the language grammar).



      Out of all the flow control statements, this didactic language only has if, else, and while statements (so no do while, for, switch cases, etc). Note that else if is also possible. The following are all valid example snippets:



      a)



      if (condition) {
      // non-returning commands
      }
      return value


      b)



      if (condition) {
      return value
      }
      return anotherValue


      c)



      if (condition) {
      return value1
      } else {
      return value2
      }
      // No return value needed here


      I've searched a lot about this but couldn't find a pseudoalgorithm that I could comprehend. I've searched for software path testing, the white box testing, and also other related Stack Overflow questions like this and this.



      I've heard that this can be solved using graphs, and also using a stack, but I have no idea how to implement those strategies.



      Any help with pseudocode would be very helpful!



      (and if it matters, I'm implementing my compiler in Swift)







      swift compiler-construction






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 24 '18 at 23:57







      Roger Oba

















      asked Nov 24 '18 at 23:48









      Roger ObaRoger Oba

      172113




      172113
























          2 Answers
          2






          active

          oldest

          votes


















          1














          If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.



          That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).



          We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:



          So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).



          Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.



          If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.



          So in pseudo code that would be:



          alwaysReturns( {} ) = false
          alwaysReturns( {return exp; ...rest} ) = true
          alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
          (alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
          alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)





          share|improve this answer
























          • That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

            – Roger Oba
            Nov 25 '18 at 5:12











          • @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

            – sepp2k
            Nov 25 '18 at 5:22











          • It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

            – Roger Oba
            Nov 25 '18 at 5:27











          • @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

            – sepp2k
            Nov 25 '18 at 5:42











          • This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

            – Sulthan
            Nov 25 '18 at 16:24



















          0














          So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.



          Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:



          final class SemanticAnalyzer {
          private var currentNode: Node!
          private var rootNode: Node!

          final class Node {
          var nodes: [Node] =
          var returnsExplicitly = false
          let parent: Node?
          var elseNode: Node!
          var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }

          init(parent: Node?) {
          self.parent = parent
          }

          func validate() -> Bool {
          if alwaysReturns {
          return true
          } else {
          return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
          }
          }
          }

          /// Initializes the components of the semantic analyzer.
          func startAnalyzing() {
          rootNode = Node(parent: nil)
          currentNode = rootNode
          }

          /// Execute when an `if` statement is found.
          func handleIfStatementFound() {
          let ifNode = Node(parent: currentNode)
          let elseNode = Node(parent: currentNode)
          // Assigning is not necessary if the current node returns explicitly.
          // But assigning is not allowed if the else node always returns, so we check if the current node always returns.
          if !currentNode.alwaysReturns {
          currentNode.elseNode = elseNode
          }
          currentNode.nodes += [ ifNode, elseNode ]
          currentNode = ifNode
          }

          /// Execute when an `else` statement is found.
          func handleElseStatementFound() {
          currentNode = currentNode.elseNode
          }

          /// Execute when a branch scope is closed.
          func handleBranchClosing() {
          currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
          }

          /// Execute when a function return statement is found.
          func handleReturnStatementFound() {
          currentNode.returnsExplicitly = true
          }

          /// Determine whether the function analyzed always returns a value.
          ///
          /// - Returns: whether the root node validates.
          func validate() -> Bool {
          return rootNode.validate()
          }
          }


          Basically what it does is:




          1. When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).

          2. When the else statement is found, we just switch the current node to the else node created previously in the if statement.

          3. When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.

          4. When it finds a function return statement, it can assume that the current node will always have a return value.


          Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.



          This works with nested if/else statements, as well as branches without return values at all.






          share|improve this answer

























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53463422%2fhow-to-check-that-a-function-always-return-a-value-aka-doesnt-fall-off-the-en%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            1














            If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.



            That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).



            We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:



            So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).



            Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.



            If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.



            So in pseudo code that would be:



            alwaysReturns( {} ) = false
            alwaysReturns( {return exp; ...rest} ) = true
            alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
            (alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
            alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)





            share|improve this answer
























            • That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

              – Roger Oba
              Nov 25 '18 at 5:12











            • @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

              – sepp2k
              Nov 25 '18 at 5:22











            • It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

              – Roger Oba
              Nov 25 '18 at 5:27











            • @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

              – sepp2k
              Nov 25 '18 at 5:42











            • This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

              – Sulthan
              Nov 25 '18 at 16:24
















            1














            If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.



            That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).



            We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:



            So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).



            Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.



            If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.



            So in pseudo code that would be:



            alwaysReturns( {} ) = false
            alwaysReturns( {return exp; ...rest} ) = true
            alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
            (alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
            alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)





            share|improve this answer
























            • That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

              – Roger Oba
              Nov 25 '18 at 5:12











            • @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

              – sepp2k
              Nov 25 '18 at 5:22











            • It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

              – Roger Oba
              Nov 25 '18 at 5:27











            • @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

              – sepp2k
              Nov 25 '18 at 5:42











            • This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

              – Sulthan
              Nov 25 '18 at 16:24














            1












            1








            1







            If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.



            That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).



            We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:



            So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).



            Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.



            If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.



            So in pseudo code that would be:



            alwaysReturns( {} ) = false
            alwaysReturns( {return exp; ...rest} ) = true
            alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
            (alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
            alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)





            share|improve this answer













            If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.



            That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).



            We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:



            So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).



            Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.



            If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.



            So in pseudo code that would be:



            alwaysReturns( {} ) = false
            alwaysReturns( {return exp; ...rest} ) = true
            alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
            (alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
            alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)






            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 25 '18 at 4:59









            sepp2ksepp2k

            296k38596613




            296k38596613













            • That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

              – Roger Oba
              Nov 25 '18 at 5:12











            • @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

              – sepp2k
              Nov 25 '18 at 5:22











            • It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

              – Roger Oba
              Nov 25 '18 at 5:27











            • @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

              – sepp2k
              Nov 25 '18 at 5:42











            • This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

              – Sulthan
              Nov 25 '18 at 16:24



















            • That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

              – Roger Oba
              Nov 25 '18 at 5:12











            • @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

              – sepp2k
              Nov 25 '18 at 5:22











            • It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

              – Roger Oba
              Nov 25 '18 at 5:27











            • @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

              – sepp2k
              Nov 25 '18 at 5:42











            • This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

              – Sulthan
              Nov 25 '18 at 16:24

















            That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

            – Roger Oba
            Nov 25 '18 at 5:12





            That's an interesting way of thinking! 5 hours after posting this question, I figured out a solution (that I couldn't break until now so I assume it's right haha), but it's still great to see other points of view. What I would just correct in your analysis, is that the while loops matter if the condition is always true, e.g. while (true) { return value }; // Dead code from now on, do you agree? :)

            – Roger Oba
            Nov 25 '18 at 5:12













            @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

            – sepp2k
            Nov 25 '18 at 5:22





            @RogerOba Yeah, my simplifying assumption was basically that we treat all conditions as if they could always be true or false. If you want to handle while(true) specially, then yes, you'll need to care whether the loop contains a return. Note that then you'll also need to care about break and continue (which I assume your language must have for while(true) to even make sense).

            – sepp2k
            Nov 25 '18 at 5:22













            It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

            – Roger Oba
            Nov 25 '18 at 5:27





            It actually doesn't haha and I didn't handle the while(true) because I can't tell if the condition is always true at compile-time (with the current implementation). 😁

            – Roger Oba
            Nov 25 '18 at 5:27













            @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

            – sepp2k
            Nov 25 '18 at 5:42





            @RogerOba If it's literally while(true), you know that it's always true, but if it's while(someExp), checking whether someExp is always true would be undecidable in the general case.

            – sepp2k
            Nov 25 '18 at 5:42













            This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

            – Sulthan
            Nov 25 '18 at 16:24





            This can be actually very complicated the more complicated is the language syntax. Possibly it has to understand inner functions and closures, it has to understand enums to see whether all cases in a switch are covered. It has understand functions that never return (fatalError). It's not an easy job to do without actual syntax/semantic parsing.

            – Sulthan
            Nov 25 '18 at 16:24













            0














            So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.



            Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:



            final class SemanticAnalyzer {
            private var currentNode: Node!
            private var rootNode: Node!

            final class Node {
            var nodes: [Node] =
            var returnsExplicitly = false
            let parent: Node?
            var elseNode: Node!
            var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }

            init(parent: Node?) {
            self.parent = parent
            }

            func validate() -> Bool {
            if alwaysReturns {
            return true
            } else {
            return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
            }
            }
            }

            /// Initializes the components of the semantic analyzer.
            func startAnalyzing() {
            rootNode = Node(parent: nil)
            currentNode = rootNode
            }

            /// Execute when an `if` statement is found.
            func handleIfStatementFound() {
            let ifNode = Node(parent: currentNode)
            let elseNode = Node(parent: currentNode)
            // Assigning is not necessary if the current node returns explicitly.
            // But assigning is not allowed if the else node always returns, so we check if the current node always returns.
            if !currentNode.alwaysReturns {
            currentNode.elseNode = elseNode
            }
            currentNode.nodes += [ ifNode, elseNode ]
            currentNode = ifNode
            }

            /// Execute when an `else` statement is found.
            func handleElseStatementFound() {
            currentNode = currentNode.elseNode
            }

            /// Execute when a branch scope is closed.
            func handleBranchClosing() {
            currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
            }

            /// Execute when a function return statement is found.
            func handleReturnStatementFound() {
            currentNode.returnsExplicitly = true
            }

            /// Determine whether the function analyzed always returns a value.
            ///
            /// - Returns: whether the root node validates.
            func validate() -> Bool {
            return rootNode.validate()
            }
            }


            Basically what it does is:




            1. When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).

            2. When the else statement is found, we just switch the current node to the else node created previously in the if statement.

            3. When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.

            4. When it finds a function return statement, it can assume that the current node will always have a return value.


            Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.



            This works with nested if/else statements, as well as branches without return values at all.






            share|improve this answer






























              0














              So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.



              Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:



              final class SemanticAnalyzer {
              private var currentNode: Node!
              private var rootNode: Node!

              final class Node {
              var nodes: [Node] =
              var returnsExplicitly = false
              let parent: Node?
              var elseNode: Node!
              var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }

              init(parent: Node?) {
              self.parent = parent
              }

              func validate() -> Bool {
              if alwaysReturns {
              return true
              } else {
              return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
              }
              }
              }

              /// Initializes the components of the semantic analyzer.
              func startAnalyzing() {
              rootNode = Node(parent: nil)
              currentNode = rootNode
              }

              /// Execute when an `if` statement is found.
              func handleIfStatementFound() {
              let ifNode = Node(parent: currentNode)
              let elseNode = Node(parent: currentNode)
              // Assigning is not necessary if the current node returns explicitly.
              // But assigning is not allowed if the else node always returns, so we check if the current node always returns.
              if !currentNode.alwaysReturns {
              currentNode.elseNode = elseNode
              }
              currentNode.nodes += [ ifNode, elseNode ]
              currentNode = ifNode
              }

              /// Execute when an `else` statement is found.
              func handleElseStatementFound() {
              currentNode = currentNode.elseNode
              }

              /// Execute when a branch scope is closed.
              func handleBranchClosing() {
              currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
              }

              /// Execute when a function return statement is found.
              func handleReturnStatementFound() {
              currentNode.returnsExplicitly = true
              }

              /// Determine whether the function analyzed always returns a value.
              ///
              /// - Returns: whether the root node validates.
              func validate() -> Bool {
              return rootNode.validate()
              }
              }


              Basically what it does is:




              1. When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).

              2. When the else statement is found, we just switch the current node to the else node created previously in the if statement.

              3. When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.

              4. When it finds a function return statement, it can assume that the current node will always have a return value.


              Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.



              This works with nested if/else statements, as well as branches without return values at all.






              share|improve this answer




























                0












                0








                0







                So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.



                Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:



                final class SemanticAnalyzer {
                private var currentNode: Node!
                private var rootNode: Node!

                final class Node {
                var nodes: [Node] =
                var returnsExplicitly = false
                let parent: Node?
                var elseNode: Node!
                var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }

                init(parent: Node?) {
                self.parent = parent
                }

                func validate() -> Bool {
                if alwaysReturns {
                return true
                } else {
                return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
                }
                }
                }

                /// Initializes the components of the semantic analyzer.
                func startAnalyzing() {
                rootNode = Node(parent: nil)
                currentNode = rootNode
                }

                /// Execute when an `if` statement is found.
                func handleIfStatementFound() {
                let ifNode = Node(parent: currentNode)
                let elseNode = Node(parent: currentNode)
                // Assigning is not necessary if the current node returns explicitly.
                // But assigning is not allowed if the else node always returns, so we check if the current node always returns.
                if !currentNode.alwaysReturns {
                currentNode.elseNode = elseNode
                }
                currentNode.nodes += [ ifNode, elseNode ]
                currentNode = ifNode
                }

                /// Execute when an `else` statement is found.
                func handleElseStatementFound() {
                currentNode = currentNode.elseNode
                }

                /// Execute when a branch scope is closed.
                func handleBranchClosing() {
                currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
                }

                /// Execute when a function return statement is found.
                func handleReturnStatementFound() {
                currentNode.returnsExplicitly = true
                }

                /// Determine whether the function analyzed always returns a value.
                ///
                /// - Returns: whether the root node validates.
                func validate() -> Bool {
                return rootNode.validate()
                }
                }


                Basically what it does is:




                1. When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).

                2. When the else statement is found, we just switch the current node to the else node created previously in the if statement.

                3. When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.

                4. When it finds a function return statement, it can assume that the current node will always have a return value.


                Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.



                This works with nested if/else statements, as well as branches without return values at all.






                share|improve this answer















                So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.



                Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:



                final class SemanticAnalyzer {
                private var currentNode: Node!
                private var rootNode: Node!

                final class Node {
                var nodes: [Node] =
                var returnsExplicitly = false
                let parent: Node?
                var elseNode: Node!
                var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }

                init(parent: Node?) {
                self.parent = parent
                }

                func validate() -> Bool {
                if alwaysReturns {
                return true
                } else {
                return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
                }
                }
                }

                /// Initializes the components of the semantic analyzer.
                func startAnalyzing() {
                rootNode = Node(parent: nil)
                currentNode = rootNode
                }

                /// Execute when an `if` statement is found.
                func handleIfStatementFound() {
                let ifNode = Node(parent: currentNode)
                let elseNode = Node(parent: currentNode)
                // Assigning is not necessary if the current node returns explicitly.
                // But assigning is not allowed if the else node always returns, so we check if the current node always returns.
                if !currentNode.alwaysReturns {
                currentNode.elseNode = elseNode
                }
                currentNode.nodes += [ ifNode, elseNode ]
                currentNode = ifNode
                }

                /// Execute when an `else` statement is found.
                func handleElseStatementFound() {
                currentNode = currentNode.elseNode
                }

                /// Execute when a branch scope is closed.
                func handleBranchClosing() {
                currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
                }

                /// Execute when a function return statement is found.
                func handleReturnStatementFound() {
                currentNode.returnsExplicitly = true
                }

                /// Determine whether the function analyzed always returns a value.
                ///
                /// - Returns: whether the root node validates.
                func validate() -> Bool {
                return rootNode.validate()
                }
                }


                Basically what it does is:




                1. When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).

                2. When the else statement is found, we just switch the current node to the else node created previously in the if statement.

                3. When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.

                4. When it finds a function return statement, it can assume that the current node will always have a return value.


                Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.



                This works with nested if/else statements, as well as branches without return values at all.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Nov 25 '18 at 16:19

























                answered Nov 25 '18 at 5:19









                Roger ObaRoger Oba

                172113




                172113






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53463422%2fhow-to-check-that-a-function-always-return-a-value-aka-doesnt-fall-off-the-en%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    404 Error Contact Form 7 ajax form submitting

                    How to know if a Active Directory user can login interactively

                    TypeError: fit_transform() missing 1 required positional argument: 'X'