Pragiva — Tecnologia na prática

Tipos de dados em AdvPL: as oito letras do ValType e como converter entre elas

Tipos de dados em AdvPL: as oito letras do ValType, como o AdvPL identifica cada tipo, como converter sem surpresa e o que a declaração com As garante.

Desenvolvimento Tutorial 5 min de leitura

Assista o vídeo

Resposta rápida

Tipos
oito, cada um com uma letra — C, N, L, D, O, U, B e A.
Como descobrir
ValType(xValor) devolve a letra do valor que está na variável agora.
Data no banco
vai como texto AAAAMMDD; DToS() e SToD() fazem a ida e a volta.
Tipagem com As
documenta e deixa o compilador recusar atribuição claramente errada; não transforma a variável em cofre.
Nesta página8 seções

No AdvPL o tipo pertence ao valor, não à variável. A mesma xRet pode começar numérica, virar texto três linhas abaixo e terminar Nil. Isso é flexível, e é por isso que type mismatch está entre os erros de execução mais frequentes em customização. Este artigo mostra os tipos, como testar e como converter.

Onde a variável vive — Local, Private, Static — é outro assunto, em Escopo de variáveis em AdvPL.

A tabela

TipoLetraLiteral de exemploPrefixo usual
CaractereC"PGV-001"c
NuméricoN1250.75n
LógicoL.T. / .F.l
DataDSToD("20260917")d
ObjetoOFWTemporaryTable():New("TRB")o
IndefinidoUNilx ou u
Bloco de códigoB`{x
ArrayA{ "A", 1, .T. }a

Data não tem literal: ela nasce de função (Date(), SToD(), CToD()) ou de campo de tabela. O prefixo é convenção, não regra da linguagem — mas é ela que permite ler nTotal + cDesc e ver o erro antes de compilar.

Array tem artigo próprio: Arrays em AdvPL.

ValType e o teste antes do uso

ValType() recebe o valor e devolve a letra. É o teste que protege código que recebe dado de fora — parâmetro opcional, retorno de função de terceiro, posição de PARAMIXB:

ADVPL / TLPP
User Function PGV003(xQtd)

    Local nQtd := 0

    If ValType(xQtd) == "N"
        nQtd := xQtd
    ElseIf ValType(xQtd) == "C"
        nQtd := Val(xQtd)
    EndIf
    // U (parâmetro não informado) cai aqui com nQtd zerado

Return nQtd

Não confunda com Type(), que recebe o nome entre aspas e avalia a expressão — é a que se usa para saber se uma Private de outra rotina existe: Type("aCols") == "A". ValType(aCols) com aCols inexistente estoura erro antes de o teste acontecer. O caso real está em PARAMIXB, o array que às vezes não é seu.

Conversões que você vai escrever toda semana

ADVPL / TLPP
Local nValor := 1250.75
Local dHoje  := Date()
Local cTexto := ""

cTexto := cValToChar(nValor)                // "1250.75"
cTexto := Str(nValor, 10, 2)                // "   1250.75" — com espaços à esquerda
cTexto := AllTrim(Str(nValor, 10, 2))       // "1250.75"
cTexto := StrZero(42, 6)                    // "000042"
cTexto := Transform(nValor, "@E 999,999.99") // "  1.250,75"

nValor := Val("1250.75")                    // 1250.75
nValor := Val("1250,75")                    // 1250 — a vírgula encerra a leitura

cTexto := DToS(dHoje)                       // "20260917"
dHoje  := SToD("20260917")
cTexto := DToC(dHoje)                       // "17/09/2026" ou "17/09/26"
dHoje  := CToD("17/09/2026")

Quatro observações que valem o bloco inteiro:

  1. cValToChar aceita qualquer tipo simples — número, data, lógico — e é a escolha certa para log. Concatenar número direto em string é o type mismatch mais comum que existe.
  2. Str alinha à direita. Sem AllTrim, a chave de busca sai com espaços e o DbSeek não acha nada.
  3. Val não entende vírgula decimal. Valor digitado ou vindo de planilha em formato brasileiro precisa de StrTran(cValor, ",", ".") antes, e de tirar o ponto de milhar antes disso.
  4. DToC e CToD dependem do formato de data da sessão (ano com dois ou quatro dígitos, ordem dia/mês). Servem para mostrar ao usuário. Para gravar, comparar ou montar SQL, use DToS/SToD, que não mudam nunca.

Data no banco é texto

O dicionário diz D; no banco ela vira texto de 8 posições, AAAAMMDD. É por isso que em SQL a data se compara como string, e é por isso que DToS existe:

Confira no seu ambienteSQL
SELECT X3_CAMPO, X3_TIPO, X3_TAMANHO
FROM SX3010
WHERE X3_CAMPO IN ('C5_EMISSAO', 'C5_NUM')
  AND D_E_L_E_T_ = ' '

Na workarea, SC5->C5_EMISSAO já chega convertido para tipo D. Numa query embarcada, chega como texto e precisa de SToD — ou de TCSetField — antes de qualquer conta. O detalhe fica em Consultas SQL em AdvPL.

Aritmética de datas

Data mais número dá data; data menos data dá número de dias:

ADVPL / TLPP
Local dVenc := dDataBase + 30                  // trinta dias corridos
Local nDias := dVenc - dDataBase               // 30
Local dMes  := MonthSum(SToD("20260131"), 1)   // 28/02/2026, não 31/02

Somar dias com + é uso corrente no próprio padrão — em FN694Consol(), uma linha soma dDatabase + 1 assim, sem cerimônia. O que não funciona com + é mês e ano: + 30 não é “um mês”, porque nem todo mês tem o mesmo número de dias. Para isso existe MonthSum(dDate, nMonth), ao lado de DaySum(dDate, nDays).

E lembre que dDataBase é a data informada no login, não a do servidor — Date() é a do AppServer. Rotina agendada que usa uma no lugar da outra processa o dia errado.

Bloco de código e Eval

Bloco de código é uma função sem nome guardada numa variável. Ele só executa quando alguém chama Eval(), que devolve o valor da última expressão:

ADVPL / TLPP
Local bDesc  := {|nBase, nPerc| nBase - (nBase * nPerc / 100) }
Local nFinal := Eval(bDesc, 200, 10)   // 180

Local bLog := {|| ConOut("PGV003 " + cValToChar(nFinal)), .T. }
Eval(bLog) // loga e devolve.T.

É assim que o framework recebe regra sua: validação de campo, ação de botão, filtro de browse. Quando uma rotina padrão pede “um bloco”, ela vai chamar Eval nele no momento dela, não no seu. Um bloco pode ler as variáveis Local de onde foi criado, e isso é o que o torna útil — e o que torna difícil depurar quando ele roda longe dali.

Objeto e Nil

Tipo O é o que sai de um construtor (:New()). Tipo U é a ausência de valor: variável declarada sem atribuição, parâmetro não passado, função que retorna Nil. Todo objeto que você cria merece FreeObj(oObj) no fim, e todo parâmetro opcional merece um teste de U logo na entrada da função — a ordem dessas declarações está em Estrutura de um fonte AdvPL.

Tipagem declarada com As

A declaração pode dizer o tipo:

ADVPL / TLPP
Local cCliente As Character
Local nTotal   As Numeric
Local lOk      As Logical
Local dEmissao As Date
Local oModelo  As Object
Local bRegra   As Codeblock
Local aItens   As Array
Local xRet     As Variant

Não é novidade de TLPP: o próprio AdvPL já usa As Character amplamente, em funções como AF012Inv(), e As Variant para o que aceita qualquer tipo, como em FINFK5BUSCA() — um sinal de que o parâmetro recebe mais de um tipo dependendo de quem chama, e que quem lê a função precisa testar antes de usar.

O que ela garante: o leitor sabe o que esperar, e o compilador recusa uma atribuição cujo tipo ele consegue ver e que não bate — um literal numérico numa variável Character, por exemplo.

O que ela não garante: o compilador não conhece o tipo do retorno de toda função, de campo de tabela, de Private alheia nem de posição de array. Valor que chega por esses caminhos passa. Continue testando com ValType tudo que vem de fora; As complementa o teste, não o substitui.

Quando o fonte com tipagem não compila fora do TDS, as causas usuais estão em Oito armadilhas ao compilar AdvPL e TLPP fora do TDS. O próximo passo da série é o que se faz com esses valores: Operadores em AdvPL.


Se um type mismatch está custando tempo e você quer uma segunda leitura, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — ValType
  2. TDNtdn.totvs.com

    TDN — cValToChar
  3. TDNtdn.totvs.com

    TDN — DToS
  4. TDNtdn.totvs.com

    TDN — CToD
  5. TDNtdn.totvs.com

    TDN — Transform
  6. TDNtdn.totvs.com

    TDN — Eval

Continue por aqui