AnyMath 文档

变量的作用域

变量的_scope_是可访问变量的代码区域。 变量作用域有助于避免变量命名冲突。 这个概念是直观的:两个函数都可以有参数调用 x 没有两个 x我指的是同一件事。 同样,还有许多其他情况,不同的代码块可以使用相同的名称而不引用相同的东西。 当同一变量名引用或不引用同一事物时的规则称为作用域规则;本节将详细说明它们。

语言中的某些构造引入了_scope blocks_,这是代码区域,有资格成为某组变量的范围。 变量的作用域不能是任意的源行集;相反,它将始终与这些块中的一个对齐。 Julia中主要有两种类型的作用域,global scope_和_local scope。 后者可以嵌套。 在Julia中,引入"硬范围"的构造和只引入"软范围"的构造之间也有区别,这会影响是否https://en.wikipedia.org/wiki/Variable_shadowing[阴影]允许或不允许同名的全局变量。

在全局作用域中定义的摘要变量可能在内部局部作用域中未定义,具体取决于代码的运行位置,以平衡安全性和便利性。 硬和软局部范围界定规则定义了全局变量和局部变量之间的相互作用。

但是,仅在局部作用域中定义的变量在所有上下文中都表现一致。 如果变量已经定义,它将被重用。 如果未定义变量,则它将可用于当前和内部作用域(但不是外部作用域)。

一个常见的混乱,如果你遇到一个意外未定义的变量,

#打印数字1到5
i=0
当我<5
    I+=1#错误:UndefVarError: `i` 未定义
    打印(i)
结束

一个简单的修复是通过将代码包装在一个 块或 功能.

#打印数字1到5
让i=0
    当我<5
        I+=1#现在外 `i` 在while循环的内部范围中定义
        打印(i)
    结束
结束

在编写过程脚本时,这是一个常见的混淆来源,但如果代码在函数内部移动或在REPL中以交互方式执行,则它将成为一个非问题。

另见[全球](/base/base#global)和[本地](/base/base#local)关键字以显式实现任何所需的范围界定行为。

范围构造

引入范围块的构造是:

建造工程 范围类型介绍 能够包含Construct的作用域类型

模块, 裸模,裸模

全球

全球

结构体

本地(硬)

全球

本地(硬)

全球

, , 试试

本地(软)

全球的,本地的

功能, , , 理解, 发电机

本地(硬)

全球的,本地的

这张表中明显缺少的是 开始块if块它不会引入新的作用域。 这三种类型的作用域遵循一些不同的规则,将在下面解释。

朱莉娅使用https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scope_vs._dynamic_scope[lexical scoping],表示函数的作用域不是从其调用者的作用域继承,而是从定义函数的作用域继承。 例如,在以下代码中 x 里面 [医]脚 指的是 x 在其模块的全局范围内 酒吧:

julia> module Bar
           x = 1
           foo() = x
       end;

而不是一个 x 在范围内 [医]脚 被使用:

julia> import .Bar

julia> x = -1;

julia> Bar.foo()
1

因此_lexical scope_意味着特定代码段中的变量所指的内容可以从它单独出现的代码中推导出来,而不依赖于程序的执行方式。 嵌套在另一个作用域内的作用域可以在其所包含的所有外部作用域中"看到"变量。 另一方面,外部作用域无法看到内部作用域中的变量。

全球范围

每个模块都引入了一个新的全局范围,与所有其他模块的全局范围分开-没有包罗万象的全局范围。 模块可以通过 使用或导入语句或通过使用点符号的限定访问,即每个模块都是所谓的_namespace_以及将名称与值相关联的一流数据结构。

如果顶级表达式包含带关键字的变量声明 本地,则该变量在该表达式之外不可访问。 表达式内部的变量不影响同名的全局变量。 一个例子是声明 本地x 在一个 開始啦。如果 顶层的块:

julia> x = 1
       begin
           local x = 0
           @show x
       end
       @show x;
x = 0
x = 1

请注意,交互式提示(又名REPL)在模块的全局范围内 主要.

本地范围

大多数代码块都引入了一个新的本地作用域(见上文 为完整列表)。 如果这样的块在语法上嵌套在另一个本地作用域内,则它创建的作用域嵌套在它出现的所有本地作用域内,这些作用域最终都嵌套在计算代码的模块的全局作用域内。 外部作用域中的变量在它们包含的任何作用域中都是可见的-这意味着它们可以在内部作用域中读取和写入-除非存在具有相同名称的局部变量"阴影" 即使外部局部是在(从文本的意义上讲)内部块之后声明的,这也是如此。 当我们说一个变量在给定的作用域中"存在"时,这意味着该名称的变量存在于当前作用域嵌套的任何作用域中,包括当前作用域。

某些编程语言需要在使用新变量之前显式声明它们。 显式声明也适用于Julia:在任何本地范围内,写作 本地x 在该范围中声明一个新的局部变量,而不管是否已经有一个名为 x 是否在外部范围内。 但是,像这样声明每个新变量有点冗长和乏味,所以Julia和许多其他语言一样,考虑对不存在的变量名的赋值来隐式声明该变量。 如果当前作用域是全局的,则新变量是全局的;如果当前作用域是局部的,则新变量对最内部的局部作用域是局部的,并且在该作用域内可见,但不在其外 如果您分配给一个现有的本地,它_always_更新该现有的本地:您只能通过在一个嵌套的范围内显式声明一个新的本地来阴影一个本地 本地 关键字。 特别是,这适用于在内部函数中分配的变量,这可能会让来自Python的用户感到惊讶,其中内部函数中的赋值会创建一个新的局部,除非变量被显式声明为非

大多数情况下,这是非常直观的,但就像许多直觉行为一样,细节比人们天真地想象的要微妙得多。

何时 x=<值> 发生在局部作用域中,Julia应用以下规则根据赋值表达式发生的位置和发生的内容来决定表达式的含义 x 已经指在该位置:

  1. *现有本地:*如果 x 是_already一个局部variable_,那么现有的局部 x 被分配;

  2. *硬范围:*如果 x is_not已经是一个局部变量,赋值发生在任何硬作用域构造的内部(即在 块,函数,结构体或宏体,理解或生成器),一个新的本地命名 x 是在赋值范围内创建的;

  3. *软范围:*如果 x is_not已经是一个局部变量,并且包含赋值的所有范围构造都是软范围(循环), 试试/渔获 块),行为取决于全局变量是否 x 被定义: **如果全球 x is_undefined_,一个名为 x 是在赋值范围内创建的;

*如果全球 x 是_defined_,赋值被认为是不明确的: **在_non-interactive_contexts(files,eval)中,打印歧义警告并创建新的本地; **在_interactive_contexts(REPL,notebooks)中,全局变量 x 被分配。

您可能会注意到,在非交互式上下文中,硬作用域和软作用域行为是相同的,除了当隐式局部变量(即未用 本地x)阴影一个全局。 在交互式上下文中,为了方便起见,规则遵循更复杂的启发式。 下面的示例将深入介绍这一点。

现在您已经了解了规则,让我们来看看一些示例。 假设每个示例都在新的REPL会话中进行评估,以便每个代码段中唯一的全局变量是在该代码块中分配的全局变量。

我们将从一个很好的和明确的情况开始-在硬作用域内的赋值,在这种情况下是一个函数体,当该名称的局部变量已经存在时:

julia> function greet()
           x = "hello" # new local
           println(x)
       end
greet (generic function with 1 method)

julia> greet()
hello

julia> x # global
ERROR: UndefVarError: `x` not defined in `Main`

内的 问候 函数,赋值 x="你好" 原因 x 要成为函数作用域中的新局部变量。 有两个相关的事实:分配发生在本地范围内,并且没有现有的本地 x 变量。 自 x 是本地的,如果有一个全局命名也没关系 x 或者不是。 例如,我们在这里定义 x=123 在定义和调用之前 问候:

julia> x = 123 # global
123

朱莉娅>功能问候()
           x="你好"#新本地
           打印(x)
       结束
greet(带1个方法的通用函数)

朱莉娅>问候()
你好!

朱莉娅>x#全球
123

x问候 是局部的,全球的价值(或缺乏) x 不受呼叫的影响 问候. 硬作用域规则不关心全局是否命名 x 存在与否:分配给 x 在硬范围是本地的(除非 x 被声明为全局)。

我们将考虑的下一个明确的情况是,当已经有一个名为 x,在这种情况下 x=<值> 总是分配给这个现有的本地 x. 无论赋值发生在同一个局部作用域,同一个函数体中的内部局部作用域,还是嵌套在另一个函数(也称为a)内部的函数体内,都是如此https://en.wikipedia.org/wiki/Closure_(computer_programming)[closure]。

我们将使用 sum_to 函数,它计算从一到一的整数之和 n,作为一个例子:

function sum_to(n)
    s = 0 # new local
    for i = 1:n
        s = s + i # assign existing local
    end
    return s # same local
end

与前面的示例一样,第一个赋值到 s 在顶部的 sum_to 原因 s 作为函数体内的一个新的局部变量。 该 循环在函数作用域内有自己的内部局部作用域。 在那里 s=s+i 发生, s 已经是一个局部变量,所以赋值更新现有的 s 而不是创建一个新的本地。 我们可以打电话来测试一下 sum_to 在REPL:

julia> function sum_to(n)
           s = 0 # new local
           for i = 1:n
               s = s + i # assign existing local
           end
           return s # same local
       end
sum_to (generic function with 1 method)

julia> sum_to(10)
55

julia> s # global
ERROR: UndefVarError: `s` not defined in `Main`

s 是函数的本地 sum_to,调用函数对全局变量没有影响 s. 我们还可以看到更新 s=s+i 循环必须更新相同 s 由初始化创建 s=0 因为我们得到了整数1到10的正确和55。

让我们深入了解一个事实: 循环体在一秒钟内有自己的作用域,通过编写一个稍微更详细的变体,我们将调用它 sum_to_def,其中我们保存了总和 s+i 在变量中 t 更新前 s:

julia> function sum_to_def(n)
           s = 0 # new local
           for i = 1:n
               t = s + i # new local `t`
               s = t # assign existing local `s`
           end
           return s, @isdefined(t)
       end
sum_to_def (generic function with 1 method)

julia> sum_to_def(10)
(55, false)

此版本返回 s 和以前一样,但它也使用 @被定义 宏返回一个布尔值,指示是否有一个名为 t 在函数的最外层局部作用域中定义。 正如你所看到的,没有 t 定义在 环体。 这又是因为硬作用域规则:由于赋值到 t 发生在一个函数内部,它引入了一个硬作用域,赋值导致 t 成为一个新的局部变量,在它出现的局部范围内,即在循环体的内部。 即使有一个名为 t,这没有什么区别-硬范围规则不受全局范围中的任何内容的影响。

请注意,for循环体的局部作用域与内部函数的局部作用域没有什么不同。 这意味着我们可以重写这个例子,以便循环体被实现为对内部帮助函数的调用,它的行为方式相同:

julia> function sum_to_def_closure(n)
           function loop_body(i)
               t = s + i # new local `t`
               s = t # assign same local `s` as below
           end
           s = 0 # new local
           for i = 1:n
               loop_body(i)
           end
           return s, @isdefined(t)
       end
sum_to_def_closure (generic function with 1 method)

julia> sum_to_def_closure(10)
(55, false)

这个例子说明了几个关键点:

  1. 内部函数作用域就像任何其他嵌套的本地作用域一样。 特别是,如果变量已经是内部函数外部的局部变量,并且您在内部函数中分配给它,则会更新外部局部变量。

  2. 如果外部本地的定义发生在更新的地方,则规则保持不变。 在解析内部局部含义之前,将解析整个封闭的局部范围并确定其局部范围。

这种设计意味着您通常可以将代码移入或移出内部函数而不改变其含义,这有利于使用闭包的语言中的一些常见习语(参见 做块)。

让我们继续讨论软范围规则所涵盖的一些更模糊的情况。 我们将通过提取 问候sum_to_def 函数转换为软范围上下文。 首先,让我们把 问候 在一个 循环-这是软的,而不是硬的-并在REPL中评估它:

julia> for i = 1:3
           x = "hello" # new local
           println(x)
       end
hello
hello
hello

julia> x
ERROR: UndefVarError: `x` not defined in `Main`

自全球 x 循环被评估,软范围规则的第一个子句适用和 x 创建为本地的 循环,因此全球 x 在循环执行后保持未定义。 接下来,让我们考虑 sum_to_def 提取到全局范围,将其论点固定为 n=10

s = 0
for i = 1:10
    t = s + i
    s = t
end
s
@isdefined(t)

这个代码是做什么的? 提示:这是一个诡计多端的问题。 答案是"这取决于。"如果以交互方式输入此代码,则其行为与在函数体中的行为相同。 但是,如果代码出现在文件中,它会打印歧义警告并引发未定义的变量错误。 让我们先看看它在REPL中工作:

julia> s = 0 # global
0

julia> for i = 1:10
           t = s + i # new local `t`
           s = t # assign global `s`
       end

julia> s # global
55

julia> @isdefined(t) # global
false

REPL通过决定循环内的赋值是分配给全局变量还是根据是否定义了该名称的全局变量来创建新的局部变量,从而近似于在函数体中。 如果存在全局名称,则分配会更新它。 如果不存在全局变量,则赋值将创建一个新的局部变量。 在这个例子中,我们看到这两种情况都在起作用:

*没有全局命名 t,所以 t=s+i 创建一个新的 t 这是本地的 循环; *有一个名为 s,所以 s=t 分配给它。

第二个事实是为什么循环的执行会更改 s 第一个事实是为什么 t 循环执行后仍未定义。 现在,让我们尝试评估这个相同的代码,就像它在一个文件中一样:

julia> code = """
       s = 0 # global
       for i = 1:10
           t = s + i # new local `t`
           s = t # new local `s` with warning
       end
       s, # global
       @isdefined(t) # global
       """;

julia> include_string(Main, code)
┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable.
└ @ string:4
ERROR: LoadError: UndefVarError: `s` not defined in local scope

在这里我们使用 include_string,来评价 密码 就好像它是一个文件的内容。 我们还可以节省 密码 到一个文件,然后调用 包括 在那个文件上,结果是一样的。 正如您所看到的,这与在REPL中评估相同代码的行为完全不同。 让我们来解释一下这里发生的事情:

*全球 s 是用值定义的 0 在评估循环之前 *转让 s=t 发生在软作用域—​a 在任何函数体或其他硬作用域构造之外循环 *因此,软范围规则的第二个子句适用,并且赋值不明确,因此发出警告 *执行继续进行,使 s 本地 环体,环体 *自 s 是本地的 循环,它是未定义的,当 t=s+i 被评估,导致错误 *评估就停在那里,但如果它到了 s@被定义(t),它会回来 0错误.

这说明了作用域的一些重要方面:在一个作用域中,每个变量只能有一个含义,并且无论表达式的顺序如何,该含义都是确定的。 表达的存在 s=t 在循环中导致 s 对于循环来说是本地的,这意味着当它出现在循环的右侧时,它也是本地的。 t=s+i,即使该表达式首先出现并首先计算。 人们可以想象 s 在循环的第一行可以是全局的,而 s 在循环的第二行是局部的,但这是不可能的,因为两行在同一个范围块中,每个变量在给定范围内只能意味着一件事。

关于软范围

我们现在已经介绍了所有的本地范围规则,但在结束本节之前,也许应该说几句关于为什么在交互式和非交互式上下文中以不同的方式处理模糊的软范围情况。 有两个明显的问题可以问:

  1. 为什么它不像REPL一样到处工作?

  2. 为什么它不像在任何地方的文件中一样工作? 也许跳过警告?

在Julia≤0.6中,所有全局作用域都像当前的REPL一样工作:当 x=<值> 发生在循环中(或 试试/渔获,或 结构体 体)但在功能体之外(或 块或理解),它是根据是否一个全局命名来决定的 x 是否被定义 x 应该是循环的本地。 这种行为具有直观和方便的优点,因为它尽可能接近函数体内部的行为。 特别是,当试图调试函数的行为时,它可以很容易地在函数体和REPL之间来回移动代码。 然而,它有一些缺点。 首先,这是一个相当复杂的行为:多年来,许多人对这种行为感到困惑,并抱怨它既复杂又难以解释和理解。 公平点。 其次,也可以说是更糟糕的是,它对大规模编程不利。"当你在这样的一个地方看到一小段代码时,很清楚发生了什么:

s = 0
for i = 1:10
    s += i
end

显然,意图是修改现有的全局变量 s. 这还能意味着什么? 然而,并非所有真实世界的代码都是如此简短或如此清晰。 我们发现像下面这样的代码经常发生在野外:

x = 123

# much later
# maybe in a different file

对于i=1:10
    x="你好"
    打印(x)
结束

#很久以后
#也许在另一个文件中
 或者回到第一个 `x=123`

y=x+234

现在还不清楚这里应该发生什么。 自 x+"你好" 是一个方法错误,似乎很可能意图是为了 x 是本地的 循环。 但是运行时值和碰巧存在的方法不能用于确定变量的范围。 对于朱莉娅≤0.6的行为,特别是有人可能写了 首先循环,如果它工作得很好,但是后来当其他人在很远的地方添加一个新的全局-可能在一个不同的文件中-代码突然改变了含义,或者吵闹地中断,或者更糟的是,默默地做了错误的事情。 这种https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)["spooky action at a distance"]是好的编程语言设计应该防止的事情。

因此,在Julia1.0中,我们简化了作用域的规则:在任何局部作用域中,对尚未成为局部变量的名称的赋值都会创建一个新的局部变量。 这完全消除了软范围的概念,也消除了怪异行为的可能性。 我们发现并修复了大量由于删除软范围而导致的错误,证明了摆脱它的选择是正确的。 大家都很高兴! 不,不是真的。 因为有些人很生气,他们现在不得不写:

s = 0
for i = 1:10
    global s += i
end

你看到了吗? 全球 注释在里面? 可怕。 显然这种情况是不能容忍的。 但说真的,要求有两个主要问题 全球 对于这种顶级代码:

  1. 将函数体内部的代码复制并粘贴到REPL中以进行调试不再方便-您必须添加 全球 注释,然后再次删除它们以返回;

  2. 初学者会在没有 全球 并且不知道为什么他们的代码不起作用-他们得到的错误是 s 是未定义的,这似乎并没有启发任何碰巧犯这个错误的人。

从朱莉娅1.5开始,这个代码在没有 全球 注释在REPL或Jupyter笔记本等交互式上下文(就像Julia0.6)以及文件和其他非交互式上下文中,它会打印这个非常直接的警告:

指派给 s 在软作用域中是不明确的,因为存在同名的全局变量: s 将被视为一个新的地方。 通过使用消除歧义 本地s 抑制此警告或 全球s 赋值给现有的全局变量。

这解决了这两个问题,同时保留了1.0行为的"大规模编程"好处:全局变量对代码的含义没有可怕的影响,可能很远;在REPL复制和粘贴调试工作中,初学者 全球 注释或意外地在软范围内用局部阴影现有全局,无论如何这会令人困惑,他们会得到一个很好的清晰警告。

此设计的一个重要特性是,在没有警告的情况下在文件中执行的任何代码都将在新的REPL中以相同的方式运行。 另一方面,如果您进行REPL会话并将其保存到文件中,如果它的行为与REPL中的行为不同,那么您将收到警告。

让块

语句创建一个新的_hard scope_块(见上文),并在每次运行时引入新的变量绑定。 变量不需要立即赋值:

julia> var1 = let x
           for i in 1:5
               (i == 4) && (x = i; break)
           end
           x
       end
4

而赋值可能会将新值重新分配给现有值位置, 始终创建一个新位置。 这种差异通常并不重要,只有在通过闭包超过其范围的变量的情况下才能检测到。 该 语法接受逗号分隔的一系列赋值和变量名:

julia> x, y, z = -1, -1, -1;

julia> let x = 1, z
           println("x: $x, y: $y") # x is local variable, y the global
           println("z: $z") # errors as z has not been assigned yet but is local
       end
x: 1, y: -1
ERROR: UndefVarError: `z` not defined in local scope

赋值按顺序计算,在引入左侧的新变量之前,在作用域中对每个右侧进行评估。 因此,写一些像这样的东西是有意义的 设x=x 自二 x 变量是不同的,并且具有单独的存储。 下面是一个例子,其中的行为 是需要的:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           Fs[i] = ()->i
           global i += 1
       end

julia> Fs[1]()
3

julia> Fs[2]()
3

这里我们创建并存储两个返回变量的闭包 i. 但是,它始终是相同的变量 i,所以两个闭包行为相同。 我们可以使用 创建一个新的绑定 i:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           let i = i
               Fs[i] = ()->i
           end
           global i += 1
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

開始啦。 构造不会引入新的范围,使用零参数可能会很有用 只需引入一个新的作用域块,而不立即创建任何新的绑定:

julia> let
           local x = 1
           let
               local x = 2
           end
           x
       end
1

引入一个新的作用域块,内部局部 x 是与外部局部不同的变量 x. 这个特定的例子相当于:

julia> let x = 1
           let x = 2
           end
           x
       end
1

循环和理解

在循环和 理解,在它们的主体范围中引入的新变量被新分配给每个循环迭代,就好像循环主体被一个 块,如本例所示:

julia> Fs = Vector{Any}(undef, 2);

julia> for j = 1:2
           Fs[j] = ()->j
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

A 循环或理解迭代变量始终是一个新变量:

julia> function f()
           i = 0
           for i = 1:3
               # empty
           end
           return i
       end;

julia> f()
0

但是,重用现有局部变量作为迭代变量有时很有用。 这可以通过添加关键字方便地完成 外,外:

julia> function f()
           i = 0
           for outer i = 1:3
               # empty
           end
           return i
       end;

julia> f()
3

常量

变量的一个常见用途是为特定的、不变的值命名。 这样的变量只分配一次。 此意图可以使用 康斯特关键字:

julia> const e  = 2.71828182845904523536;

julia> const pi = 3.14159265358979323846;

可以在单个变量中声明多个变量 康斯特 声明:

julia> const a, b = 1, 2
(1, 2)

康斯特 声明只应在全局范围内使用. 编译器很难优化涉及全局变量的代码,因为它们的值(甚至它们的类型)几乎随时都可能发生变化。 如果全局变量不会改变,则添加 康斯特 声明解决了这个性能问题。

局部常量是完全不同的。 编译器能够自动确定局部变量何时是常量,因此不需要局部常量声明,实际上目前不支持。

特殊的顶级任务,例如由 功能结构体 关键字,默认为常量。

请注意 康斯特 仅影响变量绑定;变量可能绑定到可变对象(如数组),并且该对象仍可能被修改。 此外,当试图为声明为常量的变量赋值时,可能会出现以下情况:

*尝试在没有const的情况下替换常量 关键字 是不允许的:

julia> const x = 1.0
1.0

julia> x = 1
ERROR: invalid assignment to constant x. This redefinition may be permitted using the `const` keyword.

*允许对常量的所有其他定义,但可能会导致重大的重新编译:

julia> const y = 1.0
1.0

julia> const y = 2.0
2.0
兼容性

Julia1.12在julia1.12之前,对常量的重新定义支持不佳。 它仅限于重新定义相同类型的常量,并可能导致可观察到的错误行为或崩溃。 在1.12之前的julia版本中,经常重新定义是非常不鼓励的。 有关更多信息,请参阅之前julia版本的手册。

类型全局变量

兼容性

Julia1.8在Julia1.8中添加了对类型化全局变量的支持

与声明为常量类似,全局绑定也可以声明为始终为常量类型。 这可以在不使用语法分配实际值的情况下完成 全局x::T 或被指派为 x::T=123.

julia> x::Float64 = 2.718
2.718

julia> f() = x
f (generic function with 1 method)

julia> Base.return_types(f)
1-element Vector{Any}:
 Float64

对于对全局的任何赋值,Julia将首先尝试使用以下方法将其转换为适当的类型 转换/转换:

julia> global y::Int

julia> y = 1.0
1.0

julia> y
1

julia> y = 3.14
ERROR: InexactError: Int64(3.14)
Stacktrace:
[...]

类型不需要是具体的,但是具有抽象类型的注释通常对性能几乎没有好处。

一旦全局已被分配到或其类型已被设置,绑定类型不允许更改:

julia> x = 1
1

julia> global x::Int
ERROR: cannot set type for global x. It already has a value or is already set to a different type.
Stacktrace:
[...]