• No results found

In this section, we go through the µPython example from Chapter 3 (see Figure3.5), which can raise a TypeError depending on the branch taken at line 4. This compiles to M and Pf, defined as M = [ 0 LC Pf;SG f ;1 LC ∗;2 JIF 7;3 LC ’42’;4 SG x;5 JP 9;6 LC 42;7 SG x;8 CF f ;9 RET]10 Pf = [LG x; 0 intOp; 1 RET 2 ]

We show how preemptive type checking works at each stage and how the type error is preempted at the earliest possible point. The type checking process starts with a control flow analysis; its results are shown in Figure5.4.

We then show how we conclude that the edge hhM, 4i, hM, 5ii is in FailEdge. This means that if the execution moves from hM, 4i to hM, 5i, the program will eventually raise a TypeError or

s hM, 0i hM, 1i hM, 2i hM, 3i hM, 4i

line 0 0 2 2 3

instr. LC Pf SG f LC * JIF 7 LC ’42’

prev. ε hM, 0i hM, 1i hM, 2i hM, 3i

next. hM, 1i hM, 2i hM, 3i {hM, 4i, hM, 7i} hM, 5i

s hM, 5i hM, 6i hM, 7i hM, 8i hM, 9i

line 3 3 5 5 6

instr. SG x JP 9 LC 42 SG x CF f

prev. hM, 4i hM, 5i hM, 3i hM, 7i {hM, 8i, hM, 6i}

next. hM, 6i hM, 9i hM, 8i hM, 9i hPf

, 0i::hM, 9i s hPf, 0i::hM, 9i hPf, 1i::hM, 9i hPf, 2i::hM, 9i hM, 10i

line 1 1 1 6

instr. LG x intOp RET RET

prev. hM, 9i hPf, 0i::hM, 9i hPf, 1i::hM, 9i hPf, 2i::hM, 9i

next. hPf, 1i::hM, 9i hPf, 2i::hM, 9i hM, 10i

Figure 5.4: Control Flow for the µPython example 0420: Str

hhM, 4i, Ti `p tos : StrpLC1 hhM, 4i, Ti `f tos : >fSET

hhM, 10i, {hhPf, 2i :: hM, 9i, xi, hhPf, 1i :: hM, 9i, xi, ...}i ` f x : >

fEND

hhPf, 2i :: hM, 9i, {hhPf, 1i :: hM, 9i, xi, hhPf, 0i :: hM, 9i, xi, ...}i ` f x : >

fRET

hhPf, 1i :: hM, 9i, {hhPf, 0i :: hM, 9i, xi, hhM, 9i, xi, ...}i `

f tos : Int/x : > hhPf, 0i :: hM, 9i, {hhM, 9i, xi, hhM, 6i, xi, ...}i `

f x : Int hhM, 9i, {hhM, 6i, xi, hhM, 5i, tosi, ...}i `f x : Int

hhM, 6i, {hhM, 5i, tosi}i `f x : Int hhM, 5i, Ti `f tos : Int

fSG2

fRET/JP fCF2

fLG1

fintOp1/2

Figure 5.5: Derivations of present and future use types at hM, 4i and hM, 5i. In each rule the side-conditions are not shown. The rules are applied to the location at the top of the call stack.

diverge. From the definition of FailEdge, we need to show that

∀Σ0· hhM, 4i, hM, 5i, Σ0i 6∈ EdgeComp (5.22) We have derivations of the following in Figure5.5,

hhM, 4i, T∅i `f tos : > hhM, 4i, T∅i `ptos : Str hhM, 5i, T∅i `f tos : Int

Since Int 6= >, Int 6<: Str, and the fact that there can be no τr such that τr<: ⊥, we know that (5.22) holds. Similarly, we also conclude that hhM, 3i, hM, 4ii ∈ FailEdge.

The edge in (5.22) represents the transition from line 4 to line 5 in the source code. The checked semantics would therefore raise an Exception at that point. Now we insert type checks in

def f (): return intOp(x) if ∗ : raise x =’42’ else : x = 42 f ()

Figure 5.6: The transformed µPython example with preemptive type checking.

M . Since this is the program at the outermost scope, the specialisation argument is ε and specialise(M, ε) is called. According to the definition of FailEdge, specialise should insert a failure assertion at each edge hhM, 3i, hM, 4ii and hhM, 4i, hM, 5ii. However, in our imple- mentation we optimise by only inserting raise at the first point in the sequence of failing edges. Therefore the transformed bytecode for M is:

M0 = [LC Pf; SG f ; LC ∗; failIfTrue; JIF 7 + n; LC ’42’; SG x; JP 9 + n; LC 42; SG x; CF f ] where the inserted code is underlined and n is the length of the instructions in failIfTrue. This is equivalent to the high-level program shown in Figure5.6. The check is therefore inserted at the earliest point at which we can guarantee that the execution will end in a TypeError.

It is interesting to compare this example, say, with the approach used in gradual typing with unification based inference [98]. Since variable x is assigned both a Str and an Int in different locations, and is used as an Int, x would be inferred to have type Dyn and a type error could only be raised at the application of intOp. This is typical for other type systems which allow this program to be statically type checked [22,5,117]. Other static analysis approaches for dynamic languages would reject this program outright [10,18,6].

5.6

Conclusion

In this chapter we have formalised the type checking mechanism behind preemptive type check- ing. We have defined this in terms of a checked semantics for µPython that implements the type checking mechanism using inferred type information from the type inference defined in Chapter 4.

We have proven correctness and optimality properties for the checked semantics and presented an algorithm for inserting explicit type checks into a µPython program. Such a program behaves like a program running under a checked semantics when interpreted under an unchecked seman- tics. We have also illustrated the algorithms presented in this chapter on an example µPython program.

From µPython to full Python

In this chapter we describe how we make use of the algorithms developed in Chapters 4 and 5 to build a tool that implements preemptive type checking. Our tool supports a larger subset of the Python language than µPython. We then evaluate this tool with some synthetic examples and some benchmarks from the computer languages benchmarks game [4].

6.1

Introduction

We implemented the preemptive type checking tool as a Python 3.3 library that can be loaded with the target program. It can be invoked at runtime, typically during the initialisation of a program, to transform an existing function in such a way as to implement the semantics of preemptive type checking. This design decision makes our library easy to use, as we will see in Section6.2. Despite the fact that the analysis is actually performed at runtime, the techniques used are static analysis techniques and the analysis is meant to be invoked once.

We have based our implementation on Python 3.3 and we support a number of features, includ- ing:

• local and global variables (Section6.5), • the evaluation stack (Section6.6), • control structures such as while-loops, • polyadic functions, anonymous functions, • tuples, and operators without overloading, and

• some standard library functions, which we annotate with type information.

We also allow a user to explicitly add type annotations to any functions using function annota- tions [115].

We use the implementation described in this chapter to demonstrate the usefulness of preemptive type checking. We show this on both synthetic examples and selected benchmarks from the computer language benchmarks game [4]. This benchmark suite compares measurements of programs written in different programming languages.